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

# Ramp Corporate Cards & Spend Integration Contract

> Version: 1.0.0 Created: 2026-02-23 Updated: 2026-02-24 Status: 🏗️ Scaffolded (Phase 1 — gateway integration pending sandbox credentials) Owner: FA (Finance &…

**Version:** 1.0.0\
**Created:** 2026-02-23\
**Updated:** 2026-02-24\
**Status:** 🏗️ Scaffolded (Phase 1 — gateway integration pending sandbox credentials)\
**Owner:** FA (Finance & Accounting)

***

## Overview

This document defines the integration contract between Encore Health OS's FA module and Ramp for corporate card and spend management. Ramp provides spend management, corporate cards, ERP/accounting sync (vendors, transaction coding, payables), and an open developer API. This integration enables syncing card transactions into Encore Health OS for coding, approval workflows, and reconciliation.

***

## Quick Reference

| I need to…                       | Pattern                                | Location                                                  |
| -------------------------------- | -------------------------------------- | --------------------------------------------------------- |
| Connect Ramp to Encore Health OS | OAuth callback or API key registration | Edge function `ramp-connect`; table `fa_ramp_connections` |
| Sync card transactions           | Idempotent sync by Ramp transaction ID | Edge function `ramp-sync`; table `fa_card_transactions`   |
| Disconnect Ramp                  | Revoke tokens, set status disconnected | Edge function `ramp-disconnect`                           |
| Verify webhook events            | Signature verification per Ramp docs   | Edge function `ramp-webhook`; store `RAMP_WEBHOOK_SECRET` |

***

## Decision Trees / Pattern Library

* **OAuth vs API key:** Use **OAuth** when the Connect UX involves user authorization in the browser (recommended per Ramp guide). Use **API key** for server-only or headless sync when OAuth is not required.
* **Pagination:** Ramp list endpoints use limit/page or cursor; loop until all pages are fetched for the org's date range. See [v1/pagination](https://docs.ramp.com/developer-api/v1/pagination).
* **Deduplication:** Use `fa_card_transactions.ramp_transaction_id` as unique key; unique index on that column. On conflict, skip or update per product requirement (spec recommends skip for idempotency).

***

## Common Mistakes

| Pitfall                                 | Mitigation                                                                                                                                                           |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Skipping webhook signature verification | Always verify signature using `RAMP_WEBHOOK_SECRET` per [v1/api/webhooks](https://docs.ramp.com/developer-api/v1/api/webhooks); reject unsigned or invalid payloads. |
| Token refresh not handled               | Proactively refresh before `token_expires_at` or reactively on 401; set `reauth_required` when refresh fails.                                                        |
| Duplicate transactions imported         | Enforce unique index on `fa_card_transactions.ramp_transaction_id`; use upsert/skip on conflict.                                                                     |
| RLS misconfiguration                    | Use SECURITY DEFINER helpers; ensure UPDATE policies include WITH CHECK on `organization_id`.                                                                        |

***

## Pre-Flight Checklist

* [ ] Env vars set: `RAMP_CLIENT_ID`, `RAMP_CLIENT_SECRET`, `RAMP_API_KEY` (if used), `RAMP_WEBHOOK_SECRET`, `RAMP_ENVIRONMENT`.
* [ ] DB index on `fa_card_transactions.ramp_transaction_id` for deduplication.
* [ ] Test sandbox connection (request access via Ramp partnerships intake) before production.

***

## Integration Pattern

**Type:** External API Integration via Edge Functions\
**Direction:** Outbound (Encore Health OS → Ramp) + Inbound (Webhooks if supported by Ramp)\
**Authentication:** OAuth (recommended for Connect UX) and/or API key; see Ramp docs.ramp.com/developer-api/v1/guides/oauth

***

## Authentication Requirements

### Environment Matrix

| Environment | Access                               | Notes                                             |
| ----------- | ------------------------------------ | ------------------------------------------------- |
| Sandbox     | Request via Ramp partnerships intake | Test data; develop without production credentials |
| Production  | API key from Ramp onboarding form    | Active Ramp account required                      |

### Credentials

| Credential            | Purpose                                                    | Storage                                     |
| --------------------- | ---------------------------------------------------------- | ------------------------------------------- |
| `RAMP_CLIENT_ID`      | OAuth app identification                                   | Supabase Secret                             |
| `RAMP_CLIENT_SECRET`  | OAuth app secret                                           | Supabase Secret                             |
| `RAMP_API_KEY`        | Server-to-server API access (if used)                      | Supabase Secret                             |
| `RAMP_WEBHOOK_SECRET` | Webhook signature verification (if Ramp provides webhooks) | Supabase Secret                             |
| `RAMP_ENVIRONMENT`    | sandbox \| production                                      | Environment variable (non-sensitive config) |

Exact auth mechanism (OAuth scopes, API key headers) MUST be confirmed from live Ramp API documentation at docs.ramp.com/developer-api.

***

## API Endpoints

### Ramp API (Outbound)

Reference: [docs.ramp.com/developer-api](https://docs.ramp.com/developer-api)

| Area               | Purpose                                                            | Doc URL                                                                            |
| ------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| **OAuth**          | Authorization and token exchange                                   | [v1/guides/oauth](https://docs.ramp.com/developer-api/v1/guides/oauth)             |
| **Transactions**   | List/sync card transactions; filters: date range, merchant, status | [v1/api/transactions](https://docs.ramp.com/developer-api/v1/api/transactions)     |
| **Pagination**     | Cursor-based: `page_size` (2-100) + `start` (last entity ID)       | [v1/pagination](https://docs.ramp.com/developer-api/v1/pagination)                 |
| **Card Programs**  | List card programs (optional Phase 1)                              | [v1/api/card-programs](https://docs.ramp.com/developer-api/v1/api/card-programs)   |
| **Webhooks**       | Push events (e.g. transaction.created); verify signature per docs  | [v1/api/webhooks](https://docs.ramp.com/developer-api/v1/api/webhooks)             |
| **Reimbursements** | Out of scope Phase 1                                               | [v1/api/reimbursements](https://docs.ramp.com/developer-api/v1/api/reimbursements) |

Implementation must use the live API reference for exact paths, methods, request/response schemas, pagination parameters, and webhook payload/shape.

### Edge Functions (Inbound/Internal)

| Function          | Method | Purpose                                                                                                                 |
| ----------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| `ramp-connect`    | POST   | Complete OAuth callback or register API key; create/update org connection                                               |
| `ramp-sync`       | POST   | Sync card transactions for the organization's Ramp connection                                                           |
| `ramp-disconnect` | POST   | Revoke tokens, set connection status to disconnected                                                                    |
| `ramp-webhook`    | POST   | Handle Ramp webhook events; verify signature per [v1/api/webhooks](https://docs.ramp.com/developer-api/v1/api/webhooks) |

***

## Transactions Sync (Pagination and Deduplication)

* **Pagination:** Cursor-based: `page_size` (2-100, default 20) + `start` (ID of last entity). Loop until no more results. See [v1/pagination](https://docs.ramp.com/developer-api/v1/pagination). Confirmed 2026-02-24.
* **Deduplication:** Use Ramp transaction ID as unique key; unique index on `fa_card_transactions.ramp_transaction_id`. On conflict (same `ramp_transaction_id`), either skip or update existing row per product requirement (spec recommends skip for idempotency).
* **Incremental sync:** If Ramp supports filtering by `updated_at` or similar, use it to reduce payload; otherwise full date-range fetch with deduplication is acceptable for Phase 1.

***

## Data Flow

```text theme={null}
┌─────────────────┐                 ┌─────────────────┐
│   Encore Health OS    │                 │   Ramp API      │
│   Frontend      │                 │   (docs.ramp.com)│
└────────┬────────┘                 └────────┬────────┘
         │                                    │
         │ 1. Connect Ramp (OAuth or config) │
         ├──────────────────────────────────► │
         │                                    │
         │ 2. Tokens / success                │
         │◄───────────────────────────────────┤
         │                                    │
         │ 3. POST /ramp-connect              │
         │    (to Edge Function)              │
         ▼                                    │
┌─────────────────┐                           │
│   ramp-connect   │ 4. Store connection       │
│   Edge Function │     in fa_ramp_connections │
└────────┬────────┘                           │
         │                                    │
         │ 5. Sync requested                  │
         ▼                                    │
┌─────────────────┐ 6. GET/POST transactions │
│   ramp-sync      ├────────────────────────► │
│   Edge Function  │                          │
│                  │ 7. Transaction data      │
│                  │◄─────────────────────────┤
│                  │                          │
│                  │ 8. Store in fa_card_transactions
└─────────────────┘
         │
         │ 9. (Optional) Webhook from Ramp
         │◄───────────────────────────────────
         │
         ▼
┌─────────────────┐
│   ramp-webhook  │ 10. Verify signature, trigger sync or update status
└─────────────────┘
```

***

## Webhook Events

Ramp supports webhooks (see [v1/api/webhooks](https://docs.ramp.com/developer-api/v1/api/webhooks)). Implementation must confirm the following from live docs:

* **Signature verification:** Implement per Ramp's webhook docs (e.g. HMAC, webhook secret header, or JWT); store `RAMP_WEBHOOK_SECRET` in Supabase secrets.
* **Replay protection:** Use timestamp window if Ramp provides one (e.g. reject if `t` outside ±3 min).
* **Event types:** e.g. `transaction.created`, connection/revoked – document exact event names and payload fields when implementing; map to actions below.
* **Actions:** Update connection status, trigger incremental sync, or mark transactions; never log full tokens or card numbers.
* **Retries:** Ramp retries webhook delivery up to 10 times for 429/5xx responses. Endpoint must respond within 10 seconds. Implement idempotency using event `id` to handle duplicate deliveries. Confirmed 2026-02-24.

***

## Database Schema

### New Tables

**`fa_ramp_connections`**

* `id` UUID PK
* `organization_id` UUID FK, UNIQUE (one connection per org)
* Ramp entity/account identifier(s)
* Token columns (encrypted at rest) or reference to secrets
* `connection_status` TEXT – connected | disconnected | error | reauth\_required
* `last_synced_at` TIMESTAMPTZ
* `created_at`, `updated_at`
* RLS via SECURITY DEFINER helper on `organization_id`

**`fa_card_transactions`**

* `id` UUID PK
* `organization_id` UUID FK
* `ramp_connection_id` UUID FK
* `ramp_transaction_id` TEXT UNIQUE (deduplication)
* Amount, currency, date, merchant, status, etc. (match Ramp API response)
* GL/vendor coding columns (nullable)
* `created_at`, `updated_at`
* RLS on `organization_id`
* Unique index on `ramp_transaction_id`

### Modified Tables

**`fa_module_settings`**

* `ramp_integration_enabled` BOOLEAN DEFAULT false

***

## Security Considerations

### Token Storage

* **App-level credentials** (`RAMP_CLIENT_ID`, `RAMP_CLIENT_SECRET`, `RAMP_WEBHOOK_SECRET`) are stored in Supabase Secrets.
* **Per-organization OAuth tokens** (`access_token`, `refresh_token`) are stored in encrypted DB columns scoped by `organization_id` (not in shared Supabase Secrets).
* Tokens are never exposed to frontend.
* Edge functions access per-org tokens via service-role queries filtered by `organization_id`.

### Multi-Tenancy

* All Edge Function queries include `organization_id` filter
* RLS policies on `fa_ramp_connections` and `fa_card_transactions` enforce organization isolation
* Audit logging for connect, sync, disconnect, and webhook events

### PHI/PII

* Card transaction metadata (merchant, amount) is not PHI
* Do not send or log PHI to Ramp; do not log full card numbers or tokens

***

## Error Handling

### Ramp API Errors

| HTTP Status | Meaning                      | Action                                        |
| ----------- | ---------------------------- | --------------------------------------------- |
| 401         | Unauthorized / token invalid | Set status to `reauth_required` or disconnect |
| 403         | Forbidden / access revoked   | Set status to `disconnected`                  |
| 429         | Rate limited                 | Exponential backoff, retry                    |
| 5xx         | Ramp server error            | Log, retry with backoff                       |

### Retry Strategy

* Exponential backoff for 429 and 5xx uses `1s, 2s, 4s, 8s, 16s`; any `Retry-After` header takes precedence.
* Do not retry 4xx (except 429) indefinitely.
* **Rate limit:** 200 requests per 10-second rolling window (confirmed from Ramp docs 2026-02-24). Apply the same backoff sequence above on 429 responses.
* Align with FA-20 Plaid pattern: same backoff array and break on non–rate-limit errors.

***

## Logging

### Allowlisted Fields

* `organization_id`
* `ramp_connection_id` (not tokens)
* `transactions_synced`, `transactions_skipped`
* `sync_duration_ms`
* Webhook event type (not payload secrets)

### Prohibited

* `access_token`, `refresh_token`, API keys
* Full card numbers or sensitive merchant PII

***

## Testing Strategy

* **Unit:** Ramp client and transaction mapping (mocked responses); webhook signature verification with test secret.
* **Integration:** Connect and sync against Ramp sandbox (request access via [Ramp Partnerships Intake](https://ramp.com/developer-tools)); test pagination, date-range filter, and deduplication.
* **E2E:** Connect → Sync → View transactions (when UI exists).
* **RLS:** Verify tenant isolation on `fa_ramp_connections` and `fa_card_transactions`.

## Implementation Confirmation Checklist

Before marking integration complete, confirm with live Ramp docs and sandbox:

* [ ] OAuth: scopes, token refresh, and redirect/callback URL requirements.
* [ ] Transactions: exact path, query params (date range, status, limit, page/cursor), response schema; map all required fields to `fa_card_transactions`.
* [ ] Webhooks: event types, payload shape, signature algorithm and header; implement verification and idempotent handling.
* [x] Rate limit: 200 requests per 10-second rolling window (confirmed 2026-02-24); honor `Retry-After` header.
* [ ] Sandbox: request and obtain sandbox credentials; document test entity/card data if provided.

***

## Rollback Strategy

* Feature flag `ramp_integration_enabled` disables UI and sync
* Disabling flag hides Ramp options; existing data remains; no destructive migration required
* Optional: soft-delete or status column on `fa_ramp_connections` for reversible disconnect

***

## References

* **Spec:** [FA-30-ramp-corporate-cards-spend-integration.md](../../../specs/fa/specs/FA-30-ramp-corporate-cards-spend-integration.md)
* **Ramp Developer API:** [docs.ramp.com/developer-api](https://docs.ramp.com/developer-api)
* **Ramp OAuth:** [docs.ramp.com/developer-api/v1/guides/oauth](https://docs.ramp.com/developer-api/v1/guides/oauth)
* **Ramp Transactions:** [docs.ramp.com/developer-api/v1/api/transactions](https://docs.ramp.com/developer-api/v1/api/transactions)
* **Ramp Pagination:** [docs.ramp.com/developer-api/v1/pagination](https://docs.ramp.com/developer-api/v1/pagination)
* **Ramp Webhooks:** [docs.ramp.com/developer-api/v1/api/webhooks](https://docs.ramp.com/developer-api/v1/api/webhooks)
* **Ramp Developer Tools:** [ramp.com/developer-tools](https://ramp.com/developer-tools)
* **FA-12:** Expense Management (corporate card integration Phase 2)
* **FA\_TELLER\_INTEGRATION.md:** Pattern reference for external FA integration (mTLS, webhooks, edge functions)
* **FA-20-PLAID-INTEGRATION.md:** Pattern reference for OAuth, Sync API, webhook verification, rate-limit retry
