npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ramp-client

v1.0.0

Published

Production-grade TypeScript SDK for the Ramp Developer API v1 — OAuth 2.0, cursor pagination, deferred tasks, webhooks, and full resource coverage

Readme

ramp-client

Production-grade TypeScript SDK for the Ramp Developer API v1.

  • Zero dependencies — native fetch, Web Crypto API only
  • Full TypeScript — strict mode, exact optional properties, exhaustive types across all 17 resource namespaces
  • OAuth 2.0 — client credentials + authorization code flows with automatic token refresh and mutex-safe concurrent requests
  • Cursor paginationAsyncIterable<T> with .collect(), .take(n), .filter(), .find() combinators
  • Deferred tasks — async polling with exponential backoff for card/user/limit creation
  • Webhook handler — HMAC-SHA256 verification, replay-attack protection, typed event routing
  • Resilient HTTP — per-request retry with exponential backoff + jitter, 401 auto-refresh, configurable timeouts
  • ESM + CJS — dual format with full declaration maps

Installation

npm install ramp-client

Node ≥ 18 required (uses native fetch and Web Crypto).


Quick Start

import { RampClient } from "ramp-client";

const ramp = new RampClient({
  clientId: process.env.RAMP_CLIENT_ID!,
  clientSecret: process.env.RAMP_CLIENT_SECRET!,
  scopes: ["transactions:read", "cards:read", "users:read"],
});

// Fetch all SYNC_READY transactions across all pages
const transactions = await ramp.transactions
  .list({ sync_status: "SYNC_READY" })
  .collect();

// Get a single user
const user = await ramp.users.get("user-uuid");

// Create a department
const dept = await ramp.departments.create({ name: "Engineering" });

Configuration

const ramp = new RampClient({
  clientId: "...",
  clientSecret: "...",

  // Space-separated string or array — bound to the token at issuance
  scopes: ["transactions:read", "cards:write", "users:read"],

  // Override for sandbox testing (default: https://api.ramp.com)
  baseUrl: "https://demo-api.ramp.com",

  // Retry config (default: 3 retries, 500ms base delay, 30s timeout)
  maxRetries: 3,
  retryDelayMs: 500,
  timeoutMs: 30_000,

  // Optional structured logger
  logger: {
    debug: (msg, meta) => console.debug(msg, meta),
    info: (msg, meta) => console.info(msg, meta),
    warn: (msg, meta) => console.warn(msg, meta),
    error: (msg, meta) => console.error(msg, meta),
  },
});

Authentication

Client Credentials (default)

Token is fetched automatically on the first API call. Subsequent calls reuse the cached token; it is refreshed proactively 60 seconds before expiry. Concurrent calls are safe — only one token fetch fires at a time (mutex via shared Promise).

Authorization Code (partner / multi-tenant)

// Redirect the user to Ramp's authorization page, then exchange the code:
const tokenResponse = await ramp.exchangeAuthCode({
  code: req.query.code,
  redirectUri: "https://yourapp.com/oauth/callback",
});

// From this point all calls use the exchanged token.

Pre-obtained Token

// Useful for CLI tools, testing, or when you manage token storage yourself:
ramp.setAccessToken("ramp_tok_...", 864_000); // expiresInSeconds optional

Resources

Users

// List — returns AsyncIterable PageIterator
for await (const user of ramp.users.list({ department_id: "dept-uuid" })) {
  console.log(user.email, user.status);
}

// Collect all into array
const all = await ramp.users.list().collect();

// Get one
const user = await ramp.users.get("user-uuid");

// Create (deferred — user gets an invite email)
// poll=false returns DeferredTaskRef immediately (default)
const taskRef = await ramp.users.create({
  email: "[email protected]",
  first_name: "Alice",
  last_name: "Smith",
  role: "CARDHOLDER",
  department_id: "dept-uuid",
});
// poll=true waits for the task to reach SUCCESS or ERROR
const newUser = await ramp.users.create({ ... }, true);

// Update
await ramp.users.update("user-uuid", { role: "ADMIN" });

// Deactivate / reactivate
await ramp.users.deactivate("user-uuid");
await ramp.users.reactivate("user-uuid");

Important: New users are in INVITE_PENDING status until they accept the email invitation. Do not attempt card issuance until the user is USER_ACTIVE.

Cards

// List by user and status
const cards = await ramp.cards
  .list({ user_id: "user-uuid", status: "ACTIVE" })
  .collect();

// Issue a new virtual card (deferred + idempotency key required)
const taskRef = await ramp.cards.create({
  display_name: "AWS Infra",
  user_id: "user-uuid",
  spend_limit_id: "limit-uuid", // optional
  idempotency_key: crypto.randomUUID(),
});

// Suspend / unsuspend / terminate
await ramp.cards.suspend("card-uuid");
await ramp.cards.unsuspend("card-uuid");
await ramp.cards.terminate("card-uuid");

Transactions

// Full filter surface
const txns = await ramp.transactions
  .list({
    sync_status: "SYNC_READY",
    from_date: "2024-01-01T00:00:00Z",
    to_date: "2024-01-31T23:59:59Z",
    state: "CLEARED",
    entity_id: "entity-uuid", // multi-entity support
  })
  .collect();

// Update memo and accounting fields
await ramp.transactions.update("txn-uuid", {
  memo: "Q1 cloud infra",
  accounting_fields: [
    { field_id: "dept-field-uuid", option_id: "engineering-uuid" },
  ],
});

Limits (Spend Controls)

// Create a limit (deferred, requires idempotency key)
const limit = await ramp.limits.create(
  {
    display_name: "Q2 Marketing Budget",
    user_id: "user-uuid",
    balance: { amount: 500_000, currency_code: "USD" }, // $5,000.00
    idempotency_key: crypto.randomUUID(),
  },
  true,
); // poll=true → returns Limit object

// Update
await ramp.limits.update("limit-uuid", {
  balance: { amount: 1_000_000, currency_code: "USD" },
});

// Terminate (permanent, deferred)
await ramp.limits.terminate("limit-uuid", true);

Bills (Accounts Payable)

// Create a bill (auto-approved when created via API)
const bill = await ramp.bills.create({
  vendor_id: "vendor-uuid",
  amount: { amount: 250_000, currency_code: "USD" },
  invoice_number: "INV-2024-001",
  due_at: "2024-02-28T00:00:00Z",
  payment_method: "ACH",
});

// List by vendor and status
const pendingBills = await ramp.bills
  .list({
    vendor_id: "vendor-uuid",
    status: "APPROVED",
  })
  .collect();

// Void a bill
await ramp.bills.void("bill-uuid");

Accounting / ERP Sync

The core ERP integration loop:

// 1. Fetch all objects ready to sync
const readyTxns = await ramp.transactions
  .list({ sync_status: "SYNC_READY" })
  .collect();

// 2. Process in your ERP...

// 3. Report results back to Ramp
await ramp.accounting.postSyncStatus({
  idempotency_key: crypto.randomUUID(),
  syncs: readyTxns.map((t) => ({
    object_id: t.id,
    object_type: "TRANSACTION",
    sync_status: "SUCCESS",
  })),
});
// Manage GL accounts
await ramp.accounting.createGLAccounts([
  { name: "Travel Expenses", code: "6100", remote_id: "erp-gl-6100" },
  { name: "Software & SaaS", code: "6200", remote_id: "erp-gl-6200" },
]);

const glAccounts = await ramp.accounting
  .listGLAccounts({ is_active: true })
  .collect();

// Manage custom fields and options
await ramp.accounting.createField({
  name: "Department",
  input_type: "SELECT",
  is_required: true,
});

await ramp.accounting.createFieldOptions("field-uuid", [
  { name: "Engineering", code: "ENG", remote_id: "erp-dept-eng" },
  { name: "Marketing", code: "MKT", remote_id: "erp-dept-mkt" },
]);

Departments & Locations

const dept = await ramp.departments.create({ name: "Engineering" });
await ramp.departments.update(dept.id, { name: "Platform Engineering" });
await ramp.departments.delete(dept.id);

const loc = await ramp.locations.create({ name: "San Francisco HQ" });

Webhooks

// Register a webhook endpoint
const webhook = await ramp.webhooks.create({
  url: "https://yourapp.com/webhooks/ramp",
  event_types: [
    "transaction.created",
    "transaction.updated",
    "card.activated",
    "card.suspended",
    "user.created",
    "bill.created",
  ],
});

// Store webhook.secret_token securely for HMAC verification

Pagination

All list methods return a PageIterator<T> — an AsyncIterable<T> with helper methods:

const iter = ramp.transactions.list({ sync_status: "SYNC_READY" });

// Async iteration (memory-efficient for large datasets)
for await (const txn of iter) {
  await processTransaction(txn);
}

// Collect all into array
const all = await iter.collect();

// Take at most N items (stops fetching early)
const first10 = await iter.take(10);

// Filter across pages
const cleared = await iter.filter((t) => t.state === "CLEARED");

// Find first matching item
const target = await iter.find((t) => t.merchant_name === "AWS");

// Fetch a single page explicitly
const page1 = await iter.page();
const page2 = await iter.page(page1.page.next ?? undefined);

Deferred Tasks

Card issuance, user creation, and limit creation are asynchronous — the API returns a task reference immediately.

// Option 1: fire-and-forget, poll manually
const taskRef = await ramp.cards.create({
  display_name: "Travel Card",
  user_id: "user-uuid",
  idempotency_key: crypto.randomUUID(),
}); // returns { id: "task-uuid" }

// Poll the task yourself
import { DeferredPoller } from "ramp-client";
const poller = new DeferredPoller(httpClient);
const task = await poller.poll(taskRef, {
  intervalMs: 500,
  maxIntervalMs: 5_000,
  timeoutMs: 60_000,
});

// Option 2: poll automatically (poll=true)
const card = await ramp.cards.create(
  {
    display_name: "Travel Card",
    user_id: "user-uuid",
    idempotency_key: crypto.randomUUID(),
  },
  true,
); // returns Card when ready

Webhook Verification

import { WebhookHandler } from "ramp-client";
// or: import { WebhookHandler } from "ramp-client/webhooks";

const handler = new WebhookHandler({
  secret: process.env.RAMP_WEBHOOK_SECRET!,
  toleranceMs: 300_000, // 5-minute replay window (default)
});

// Register typed handlers
handler
  .on("transaction.created", async (event) => {
    const txn = event.data as { id: string; amount: number };
    await syncToERP(txn);
  })
  .on("card.suspended", (event) => {
    console.log("Card suspended:", event.data);
  })
  .on("*", (event) => {
    // Wildcard — fires for every event type
    console.log("Ramp event:", event.type, event.id);
  });

// Express.js / Hono / any framework:
app.post("/webhooks/ramp", async (req, res) => {
  try {
    await handler.handle(req.rawBody, req.headers as Record<string, string>);
    res.sendStatus(200);
  } catch (err) {
    if (err instanceof RampError && err.type === "authentication_error") {
      res.sendStatus(401);
    } else {
      res.sendStatus(500);
    }
  }
});

Error Handling

All API errors throw a RampError with a discriminated type field:

import { RampError } from "ramp-client";

try {
  await ramp.users.get("nonexistent-uuid");
} catch (err) {
  if (err instanceof RampError) {
    switch (err.type) {
      case "not_found":
        console.error("User does not exist");
        break;
      case "rate_limit_error":
        console.error(`Rate limited. Retry after ${err.retryAfterMs}ms`);
        break;
      case "authentication_error":
        console.error("Invalid credentials — check client_id/secret");
        break;
      case "authorization_error":
        console.error("Missing scope — check OAuth scopes");
        break;
      case "validation_error":
        console.error("Bad request:", err.body);
        break;
      case "server_error":
      case "network_error":
        if (err.isRetryable()) {
          // HttpClient already retried maxRetries times
        }
        break;
    }
    // Every error includes the x-trace-id for Ramp support
    console.log("Trace ID:", err.traceId);
  }
}

| Type | HTTP Status | Retryable | | ---------------------- | ----------- | ------------------ | | authentication_error | 401 | No | | authorization_error | 403 | No | | not_found | 404 | No | | validation_error | 400 | No | | rate_limit_error | 429 | Yes (auto-retried) | | server_error | 5xx | Yes (auto-retried) | | network_error | — | Yes (auto-retried) | | timeout_error | — | No |


Sandbox

const ramp = new RampClient({
  clientId: process.env.RAMP_SANDBOX_CLIENT_ID!,
  clientSecret: process.env.RAMP_SANDBOX_CLIENT_SECRET!,
  baseUrl: "https://demo-api.ramp.com",
});

Sandbox access requires a separate application — contact your Ramp account manager. Sandbox credentials are not interchangeable with production.


API Reference

RampClient

| Resource | Namespace | | -------------- | --------------------- | | Users | ramp.users | | Cards | ramp.cards | | Transactions | ramp.transactions | | Limits | ramp.limits | | Bills | ramp.bills | | Reimbursements | ramp.reimbursements | | Accounting | ramp.accounting | | Webhooks | ramp.webhooks | | Departments | ramp.departments | | Locations | ramp.locations | | Vendors | ramp.vendors | | Merchants | ramp.merchants | | Spend Programs | ramp.spendPrograms | | Statements | ramp.statements | | Business | ramp.business | | Audit Logs | ramp.auditLogs | | Cashbacks | ramp.cashbacks |


License

MIT