---
name: ledger-setup
description: First-time Ledger setup in an app's repo. Reads the pricing already in the code, connects to the developer's Ledger Sandbox, turns the pricing into ledger.catalog.json and publishes it after they approve the diff, then wires check and track into one critical flow and verifies it. Use when the user wants to add Ledger, credits, usage-based billing, metering, a paywall or "out of credits" limits with Ledger, or pastes a Ledger setup prompt. If Ledger is already set up, use ledger-catalog to change pricing or ledger-integrate to gate more of the app.
license: MIT
metadata:
  author: Ledger
  version: "1.0"
---

# Ledger setup

Take the developer from "I want Ledger" to their own code checking and spending credits in their Ledger Sandbox, in one sitting. You run the flow; two other skills do the heavy parts. `ledger-catalog` turns their pricing into `ledger.catalog.json` and publishes it. `ledger-integrate` puts check and track in their code and verifies it. Don't do either of those jobs from memory: load the skill when its phase comes.

Ledger in one paragraph: an organization publishes a **catalog** of **plans** (a monthly credit allowance and a monthly price; price 0 is a free plan), **features** (an action with a fixed credit cost per use) and **credit packs** (one-time credits for a price). Each **customer** is the app's own ID. The app calls **check** before a gated action (`allowed`, `availableCredits`, `creditCost`) and **track** after it succeeds (spends the cost once per `request_id`). Paid plans and packs are bought through Stripe Checkout, which Ledger hosts. Keys starting `ledger_test_` are the Sandbox, `ledger_live_` are Live.

## The three skills

| Skill | Does | Where it comes from |
| --- | --- | --- |
| `ledger-setup` | This flow, start to finish | https://docs.integrateledger.com/skill.md |
| `ledger-catalog` | Interview, `ledger.catalog.json`, diff, publish | The CLI writes it to `.agents/skills/ledger-catalog/SKILL.md` |
| `ledger-integrate` | SDK, check and track in one flow, verify in the Sandbox | The CLI writes it to `.agents/skills/ledger-integrate/SKILL.md` |

The other two ship inside the CLI, so they always match the CLI version that runs their commands. `ledger skills` (Phase 2) writes them into the repo; "load" a skill means read that file and follow it. If one is missing, run `ledger skills` again. Never fetch them from anywhere else, and never work from memory of them.

## Tools

- **The CLI:** `npx integrateledger-cli <command>` (the package installs a `ledger` command; in a JavaScript project the developer can also `npm install --save-dev integrateledger-cli` and run `npx ledger`). It needs Node 22.18 or later, or Bun, whatever the app is written in. It reads `LEDGER_API_KEY` from the environment, then `.env.local` or `.env`, and never prints it. Below, **`ledger`** means whichever of these runs. Don't run plain `npx ledger` without the dev dependency: that fetches an unrelated npm package.
- **The API** over HTTP for everything else, with the key loaded into the shell without printing it:

  ```bash
  set -a; . ./.env; set +a
  API="${LEDGER_API_URL:-https://api.integrateledger.com}"
  ```

`LEDGER_API_URL` points both at a local or staging Ledger; leave it unset otherwise. The CLI reads it only from the environment, not from `.env`, so export it in the shell when it's set (`export LEDGER_API_URL=...`, or the `set -a` line above).

## Ground rules

These hold the whole time, in all three skills.

- **Sandbox only.** Never use, ask for or configure a `ledger_live_` key, and never pass `--live`. Going Live is the developer's step, later, from the dashboard.
- **The key stays out of chat, code and git.** Check it only with `ledger whoami`, which never prints it. Never `cat` the `.env`, never echo the variable, never paste it into source, tests, docs or a commit. Code reads `process.env.LEDGER_API_KEY` / `os.environ["LEDGER_API_KEY"]`. Make sure `.env` is in `.gitignore` before anything else touches it.
- **Never invent a number or an ID.** A price, credit amount, credit cost or allowance you didn't find or get told is a question. Feature, plan and pack IDs in code must be the ones in `ledger.catalog.json`.
- **Publish only after a yes** to the catalog and the diff, unless the developer already said to go ahead without a review.
- **Smallest working integration first:** one flow, verified, before anything else.
- **An error response is a failure.** A 4xx or 5xx from Ledger is never "working"; read the error code and fix the cause. Two tries at any failing command, then stop and show the exact error.
- **Clean up after yourself.** Delete only the test customers you created. Never delete or change other customers, and never archive catalog items the developer didn't agree to. Stop only the processes you started, by their PID; never `pkill` or `killall` by name.

## How to talk

- Plain words: "plans", "credits", "what it costs", "when they run out". Field names stay in the JSON.
- One purpose per message: a status, a question, or the catalog for approval. Keep the others to one to three sentences.
- At most three open questions at a time, each with your proposed answer, so the developer can just say "yes". If your agent has a built-in way to ask questions with options, use it.
- Don't ask permission for harmless work (reading the repo, drafting the catalog, running `ledger whoami`). Ask for decisions and approvals.
- If a step is already done, say so in one line and move on.
- Quote errors exactly. No emoji, no hype.

## Progress

Put this checklist in your first message and keep it updated. Note the time at the start (`date +%s`); you report the time to the first successful check and track at the end.

- [ ] 1 Read the project
- [ ] 2 Connected to the Ledger Sandbox
- [ ] 3 Pricing settled and catalog approved (`ledger-catalog`)
- [ ] 4 Published to the Sandbox (`ledger-catalog`)
- [ ] 5 One flow integrated and verified (`ledger-integrate`)
- [ ] 6 Report

While `ledger-catalog` or `ledger-integrate` runs, show its checklist instead of this one, and come back here when it's done.

## Phase 1: Read the project (silent)

Don't message yet. Find out:

- **Stack:** language, framework, package manager (from the lockfile), how the server starts, where routes live, how config and env vars are read. TypeScript or JavaScript backends use the `integrateledger` npm package, Python backends the `integrateledger` PyPI package; anything else uses HTTP.
- **Who the user is:** the auth middleware or session, and the stable ID it gives you (user ID, org, team or workspace ID). Never an email.
- **Pricing already written down:** a pricing page or component, README, docs, marketing copy, plan constants, feature flags, Stripe price IDs, "credits" or "tokens" in the UI. Write down every number and where it came from.
- **What's expensive:** the routes that do the work worth charging for (generate, export, summarize, send). One of these is the critical flow.
- **Existing Stripe Billing:** `stripe.subscriptions.create`, Checkout with `mode: "subscription"`, `price_…` IDs in code or env, webhooks for `invoice.paid` or `customer.subscription.*`. If you find them, the developer probably has paying subscribers to move: see [Migrating from Stripe](#migrating-from-stripe).
- **Existing Ledger state:** a `ledger.catalog.json` or `ledger.config.ts` (Ledger is already set up: treat that file as the truth and use `ledger-catalog` for changes), `integrateledger` in the dependencies. Whether a key is set is `ledger whoami`'s job; don't open `.env` to look.

## Phase 2: Connect

Make sure `.env` is listed in `.gitignore` first; create `.gitignore` if there isn't one. Run `ledger skills`: it writes `ledger-catalog` and `ledger-integrate` into `.agents/skills/` (agents that read another folder can link them with the `npx skills add` line it prints; reading the files directly works too). Then run `ledger whoami`, and send one message: two sentences on what will happen, in your own words (you'll turn their pricing into a Ledger catalog, show it to them before anything is published, publish it to their Sandbox, and wire one flow so they can see it run), the checklist, and the connection status:

- **It names a Sandbox workspace** → "Connected to your Ledger Sandbox" and what it already holds, in one line. If the API URL isn't `https://api.integrateledger.com`, say it's a local or staging Ledger.
- **No key** → one message, then wait:

  > I need a Ledger Sandbox API key. In the [Ledger dashboard](https://dashboard.integrateledger.com), switch the sidebar to **Sandbox**, open **Settings › API keys**, create a key with Restricted mode off, and add it to `.env` as `LEDGER_API_KEY=...`. Please don't paste it here. Tell me when it's in.

  While you wait, carry on with the interview in `ledger-catalog`; nothing there needs the key until the diff.
- **A Live key, or an error** → quote the error and say what's needed (a `ledger_test_` key with full access). Don't work around it.

## Migrating from Stripe

When Phase 1 found Stripe subscriptions, ask once, with the Phase 2 message: "You already bill subscribers with Stripe. Should I move them onto Ledger too? They keep their card, price and renewal date, and nothing is charged." If no, carry on as usual. If yes, this replaces publishing in Phase 4:

1. **Connect the same Stripe account.** In the dashboard's Sandbox, **Settings › Stripe**, the developer connects the `sk_test_` key of the account their subscriptions live in (they do it; never ask for the key). Wait for it to show a healthy webhook.
2. **Dry run:** `ledger migrate stripe --dry-run --json` (add `--customer-id <metadata key>` if Phase 1 found the app's own user ID in the Stripe customer's metadata; otherwise the customer ID is the Stripe `cus_…`). Show the developer, in one message: the plans it proposes, how many customers it would import with their next renewal, and every skipped subscription with its reason.
3. **Interview** with `ledger-catalog`: settle each proposed plan's monthly credits and the features with their credit costs. Prices come from Stripe. Yearly, metered, coupon and taxed subscriptions can't be moved yet: say so, don't model them.
4. **Migrate after the yes:** `ledger migrate stripe --credits <price_…>=<credits> … --feature <id>=<cost> … --yes`. Quote its summary. If any failed, run the same command again: it resumes.
5. Continue with Phase 5, using the migrated customer IDs.

The full guide: https://docs.integrateledger.com/migrate-from-stripe

## Phases 3 and 4: Catalog

Load `ledger-catalog` now. From here it owns the conversation: the interview, `ledger.catalog.json`, the table and diff for approval, and the publish. Come back when the catalog is published to the Sandbox.

## Phase 5: Integrate one flow

Name the critical flow from Phase 1 (ask only if it isn't obvious), and say out loud that the first pass is just this one flow, so a small diff isn't mistaken for the whole integration.

Load `ledger-integrate` with that scope and the interview's answers (customer ID, new-user plan, behaviour at zero credits and when Ledger is unreachable, the feature for each tier). From here it owns the code and the verification. Come back when its verification passes.

## Phase 6: Report

One message:

- **Live in the Sandbox:** the plans, features and packs, one line each.
- **Code changed:** each file and what it does now, a line each. `ledger.catalog.json` is new; the key is in `.env` only.
- **It works:** what the verification showed (a successful call, the refusal at zero, the balance).
- **Latency:** check, track, and the route's success and refusal paths, p50 and p95 with n, and where they were measured from. Production from a server near the API will differ; say so.
- **Time to first check and track:** from the start of the session.
- **Not modelled**, and what you did instead.
- **Next:** offered, not started: the other features, buying a plan or pack (`attach` returns a Checkout URL), a billing page with the balance, the signup hook if you went lazy, and later, Live (a Live key and catalog from the dashboard; https://docs.integrateledger.com/getting-started/deploy).

Then stop. Don't keep building.
