# Zappier — Operations & Usage Manual **Version:** 0.2.0 · **Last updated:** 2026-07-28 Zappier is a metered API platform: every API call your customers make is priced per endpoint, adjusted by their customer type, tracked in a usage ledger, and billed through Stripe once a day — or invoiced manually by purchase order. Customers self-serve through the portal at `/portal`. This manual covers running and operating the system. For internals, see [DEVELOPER.md](DEVELOPER.md); for accounting procedures see [ACCOUNTING.md](ACCOUNTING.md); for the end-user view see [CUSTOMER-PORTAL.md](CUSTOMER-PORTAL.md). --- ## Table of contents 1. [Quick start](#1-quick-start) 2. [The three surfaces](#2-the-three-surfaces) 3. [How pricing works](#3-how-pricing-works) 4. [Operating the Pricing Admin UI](#4-operating-the-pricing-admin-ui) 5. [Using the public API](#5-using-the-public-api) 6. [Billing operations (Stripe)](#6-billing-operations-stripe) 7. [The Zapier integration](#7-the-zapier-integration) 8. [Day-to-day runbook](#8-day-to-day-runbook) 9. [Troubleshooting](#9-troubleshooting) --- ## 1. Quick start ```bash cd /Users/marchon/zappier npm install npm run dev # starts the API on http://localhost:3000 ``` Environment variables (all optional except `STRIPE_SECRET_KEY` for billing): | Variable | Default | Purpose | |---|---|---| | `PORT` | `3000` | HTTP port for the API server | | `ZAPPIER_DB` | `/zappier.db` | SQLite database file location | | `ADMIN_KEY` | `admin-dev-key` | Key for the admin console and admin API | | `ADMIN_USER` | `admin` | Admin console primary username | | `DEMO_ADMIN_USER` / `DEMO_ADMIN_PASSWORD` | `demo` / `$$$Adm1n###` | Demo sign-in — override in production | | `STRIPE_SECRET_KEY` | — (required for billing) | Billing job + portal reloads, loaded from `.env` | All runtime paths (database default, `.env`, OpenAPI spec, static assets) resolve from the installation root — the compiled server (`node dist/index.js`) runs from any working directory, under systemd, Docker, or cron. The `.env` file at the repo root holds `STRIPE_SECRET_KEY`. It is gitignored and owner-only (`chmod 600`). A template is in `.env.example`. On first start the database is created and seeded with: - **Rate card:** `status` (free), `storage-list` (free), `transform` (fixed 4¢), `storage` (variable: 10¢ base + 1¢/KB metadata + 50¢/MB attachments) - **Customer types:** Free (×1.0, 100¢/month credit), Pro (×0.5, 1000¢ credit), Business (×0.25, 10000¢ credit) - **Demo customers:** `key-ada` (Free), `key-grace` (Pro), `key-linus` (Business) > Seeding only happens into an **empty** database. Existing data is never > overwritten on restart. --- ## 2. The three surfaces | Surface | URL / location | Who it's for | |---|---|---| | **Public API** | `http://localhost:3000/v1/*` | Your API customers | | **Interactive API docs** | `http://localhost:3000/docs` | Developers integrating with you | | **Admin console** | `http://localhost:3000/admin` | You (operations & accounting) | | **Customer portal** | `http://localhost:3000/portal` | End-user customers (self-service) | | **Zapier app** | `zapier-app/` directory | No-code users via Zapier | ![Interactive API docs](screenshots/api-docs.png) --- ## 3. How pricing works Every priced call returns its **quote** in the response, so customers always know what a call cost: ```json { "quote": { "endpointId": "storage", "listCents": 62, "totalCents": 31, "breakdown": { "baseCents": 10, "metadataCents": 2, "attachmentCents": 50 } } } ``` The price of a call is computed in three steps: 1. **Endpoint rule** (from the rate card): - `free` — always 0¢ - `fixed` — a flat `fixedCents` per call - `variable` — `baseCents` + `perKbCents` × ceil(metadata bytes / 1024) + `perMbCents` × ceil(attachment bytes / 1 MB) 2. **Customer-type multiplier** — `totalCents = round(listCents × multiplier)`. A per-customer **multiplier override** (set in the Customers tab) wins over the type multiplier — use it for negotiated enterprise deals. 3. **Monthly credit** — at billing time, each customer's type credit (e.g. Pro = 1000¢) is subtracted from their month-to-date total. Only the excess is billed. **Worked example.** A Pro customer (×0.5) uploads a 1 MB file with 2 KB of metadata to `storage`: - list = 10¢ base + 2¢ metadata + 50¢ attachment = **62¢** - Pro multiplier: 62 × 0.5 = **31¢** charged to their usage ledger - If their month-to-date is 1500¢ and the Pro credit is 1000¢, the daily billing job reports **500¢** to Stripe. --- ## 4. Operating the Pricing Admin UI Open `http://localhost:3000/admin` and sign in. Two accounts are available: | Username | Password | Purpose | |---|---|---| | `admin` | the `ADMIN_KEY` env value (default `admin-dev-key`) | Primary operator | | `demo` | `$$$Adm1n###` (env `DEMO_ADMIN_PASSWORD`) | Demo / stakeholder access | Sessions are token-based and remembered in browser local storage until you click **Sign out** or the server restarts (tokens are in-memory — just sign in again). The legacy `x-admin-key` header still works for scripts and curl. ### 4.1 Rate card tab ![Rate card](screenshots/admin-rate-card.png) One row per API endpoint (matched by OpenAPI `operationId`). - **Change a price:** edit the kind (`free` / `fixed` / `variable`) and the cent fields, then click **Save**. Takes effect on the next API call — no restart needed. - **Add an endpoint:** enter the `operationId` (must match `openapi.yaml`), pick a kind, click **Add**. - **Delete** removes the rule. If an endpoint has no rule and the customer's type has no default rule, calls to it are rejected with 403 — deletion is how you turn an endpoint **off**. ### 4.2 Customer types tab ![Customer types](screenshots/admin-tiers.png) Types are your pricing tiers. - **Multiplier** scales every price for that type (0.5 = 50% of list). - **Monthly credit (cents)** is the free included usage per month. - **Add customer type:** id, name, multiplier. New types start with 0 credit; edit after adding. ### 4.3 Customers tab ![Customers](screenshots/admin-customers.png) - **Create** a customer: name + type. **The API key is shown once** in the status line at the bottom — copy it immediately and send it to the customer. - **Change type** with the dropdown, then **Save**. - **Email** — used for portal sign-in/claiming and email invoicing. - **Billing** — `Stripe` (metered by the daily job) or `Purchase order` (manual invoicing with PO numbers; see [ACCOUNTING.md](ACCOUNTING.md)). - **Multiplier override**: a number here replaces the type multiplier for this customer only. Leave blank to inherit from the type. - Stripe customer IDs are attached via the admin API (`PUT /admin/api/customers/:id` with `{"stripeCustomerId": "cus_..."}`) — see section 6. ### 4.4 Invoices, Reports, System tabs The **Invoices** tab generates monthly invoices from metered usage (per period, optionally per customer, with optional PO number), walks them draft → issued → paid, and opens print-ready invoice pages. The **Reports** tab produces date-ranged billing reports (all customers, one customer, or one billing type) with CSV download, plus daily/weekly usage-trend charts. The **System** tab shows Zapier integration health and the current-period billing snapshot. Full procedures: [ACCOUNTING.md](ACCOUNTING.md). ![Invoices](screenshots/admin-invoices.png) --- ## 4A. Customer portal Your customers self-serve at `http://localhost:3000/portal`: signup (or claiming an account you created, by email), sign-in with optional TOTP two-factor authentication, month-to-date usage, invoice history with print view, prepaid balance reloads (drawn down automatically at invoice issue), email-invoicing preferences, API-key regeneration, and live pricing. The full end-user guide is [CUSTOMER-PORTAL.md](CUSTOMER-PORTAL.md). --- ## 5. Using the public API All calls need the customer's API key in the `x-api-key` header. Interactive docs with a "Try it out" console are at `/docs`. ```bash # Free status check curl -H 'x-api-key: key-ada' http://localhost:3000/v1/status # Fixed-price call (4¢ list) curl -X POST -H 'x-api-key: key-grace' -H 'content-type: application/json' \ -d '{"text":"hello"}' http://localhost:3000/v1/transform # Variable-price call: metadata + attachments curl -X POST -H 'x-api-key: key-grace' \ -F 'metadata={"title":"Q3 report"}' \ -F 'attachments=@report.pdf' \ http://localhost:3000/v1/storage # List your stored items (free) curl -H 'x-api-key: key-grace' http://localhost:3000/v1/storage # Your month-to-date usage, with credit applied curl -H 'x-api-key: key-grace' http://localhost:3000/v1/usage ``` Limits & validation: requests are validated against `openapi.yaml` (bad requests get 400); attachments are capped at **25 MB per file**; `metadata` must be valid JSON (400 otherwise). --- ## 6. Billing operations (Stripe) ### 6.1 How it works A scheduled job runs the billing reporter **daily at 06:17 America/New_York** (Kimi cron job "Zappier billing · report usage to Stripe"). For each customer with a Stripe ID it: 1. Sums their usage since the 1st of the month, subtracts their type's monthly credit → **billable cents**. 2. Reports only the **delta** above what was already reported this month to Stripe as a meter event (`zappier.api_cents`, value = cents). 3. Records the new cumulative total in the `billing_reports` ledger. Re-running is always safe: the ledger makes repeats no-ops, a per-run lock prevents overlapping executions, and a deterministic Stripe `identifier` (`customer:period:billable`) dedupes crash retries. ### 6.2 One-time Stripe setup (test mode) 1. Dashboard (test mode ON) → **Billing → Meters → Create meter**: event name `zappier.api_cents`, aggregation **Sum** of `value`, customer mapping `stripe_customer_id`. 2. **Product catalog → Add product** "Zappier API usage" → price: recurring, monthly, metered against that meter, **$0.01 per unit** (1 unit = 1 cent). 3. For each billable customer: create the Stripe Customer, attach a payment method, and add a **subscription** with the metered price. Meter events for customers without a metered subscription are recorded but never invoiced. 4. Put the `sk_test_...` key into `.env` (replace the placeholder). 5. Attach Stripe IDs to Zappier customers: ```bash curl -X PUT -H 'x-admin-key: admin-dev-key' -H 'content-type: application/json' \ -d '{"stripeCustomerId":"cus_..."}' \ http://localhost:3000/admin/api/customers/cust_2 ``` ### 6.3 Verifying a run ```bash npx ts-node src/jobs/report-usage.ts ``` Expected output per customer: - `skip (nothing to report)` — no billable usage yet - `skip (already reported Nc)` — no new usage since last run - `: reported N billable cents to Stripe` — delta sent - `report-usage: another run holds the lock, abort run` — safe concurrent abort Then check the meter's **Events** tab in the Stripe dashboard and the test customer's **upcoming invoice**. ### 6.4 Going live Repeat 6.2 steps 1–3 in live mode, replace `.env` with the `sk_live_...` key from the **same Stripe account**, and keep the same cron. The live product `prod_Uxv9SAeIOZzyx1` (currently deactivated) can be reactivated or recreated. --- ## 7. The Zapier integration The `zapier-app/` directory contains the Zapier Platform app: - **Authentication:** API key — the user pastes their API base URL (e.g. `http://localhost:3000`) and their `key-...` customer key; the connection is tested against `/v1/status`. - **Trigger "New Item":** polls `GET /v1/storage` for newly stored items. - **Action "Store Data":** calls `POST /v1/storage` with metadata and optional file attachments — billed per the rate card. To publish: create a Zapier developer account, `cd zapier-app && npm install && zapier login && zapier push`, then share the app or submit it to the Zapier marketplace. --- ## 8. Day-to-day runbook | Task | How | |---|---| | Change a price | Admin console → Rate card → Save | | Add a customer | Admin console → Customers → Create → copy the one-time API key | | Set a customer's billing type/email | Customers tab → Billing dropdown / Email field → Save | | Invoice a period | Invoices tab → Generate → Issue → Mark paid (ACCOUNTING.md) | | Export accounting data | Reports tab → filters → Download CSV | | Give a customer a deal | Customers tab → multiplier override, or a new customer type | | Turn an endpoint off | Rate card → Delete (calls get 403) | | Check a customer's usage | Their portal dashboard, `GET /v1/usage` with their key, or query `zappier.db` | | Check billing ran | Kimi notification after each 06:17 run; or run the job manually | | Backup | Copy `zappier.db` (SQLite, single file) | | Update dependencies | `npm outdated`, then `npm test` must stay green (165 tests) | --- ## 9. Troubleshooting | Symptom | Likely cause | Fix | |---|---|---| | `401 invalid or missing API key` | Wrong/absent `x-api-key` | Re-issue key from Customers tab | | `403 No price rule for ...` | Endpoint deleted from rate card and tier has no default rule | Re-add the rule, or set a tier `defaultRule` | | `403 Unknown tier` | Customer's `tierId` doesn't exist | Fix the customer's type in the admin UI | | `400 invalid metadata JSON` | `metadata` form field isn't valid JSON | Send e.g. `{"key":"value"}` | | `413 ... file too large` | Attachment over 25 MB | Split or compress the file | | Billing run: Stripe auth error | Placeholder/wrong key in `.env`, or key from a different Stripe account | Use the `sk_test`/`sk_live` key from the account that holds the meter | | Stripe shows `METER_NOT_FOUND` invalid events | Meter missing or event name mismatch | Meter event name must be exactly `zappier.api_cents` | | `another run holds the lock, abort run` | Overlapping runs, or a crash left a stale lock | Safe by design; stale locks expire after 1 hour | | Admin UI: "invalid username or password" | Wrong credentials, or env overrides changed them | Check `ADMIN_KEY` / `DEMO_ADMIN_PASSWORD`; sign in again | | Admin UI: "Session expired" | Server restarted (admin tokens are in-memory) | Sign in again | | Portal login: `totp_required` | 2FA is enabled on the account | Enter the current 6-digit authenticator code | | Portal: "invalid or expired session" | 7-day session expired | Sign in again | | Portal reload didn't credit | Real `STRIPE_SECRET_KEY` configured → PaymentIntent awaits confirmation | Balance credits when the payment confirms; in dev (placeholder key) credit is instant | | Invoice went straight to `paid` on Issue | Customer's prepaid balance fully covered the amount due | Working as designed — balance was drawn down |