134 lines
5.7 KiB
Markdown
134 lines
5.7 KiB
Markdown
# Zappier
|
||
|
||
Metered API platform: per-endpoint pricing, customer types with multipliers and
|
||
monthly credits, a usage ledger, Stripe metered billing, purchase-order
|
||
invoicing, a company admin console, a self-service customer portal, and a
|
||
Zapier integration.
|
||
|
||
## Documentation
|
||
|
||
- **[docs/USER-MANUAL.md](docs/USER-MANUAL.md)** — operations & usage manual
|
||
- **[docs/ACCOUNTING.md](docs/ACCOUNTING.md)** — company accounting: invoices, PO billing, reports, CSV
|
||
- **[docs/USER-MANAGEMENT.md](docs/USER-MANAGEMENT.md)** — pricing, customer types, customer accounts
|
||
- **[docs/CUSTOMER-PORTAL.md](docs/CUSTOMER-PORTAL.md)** — end-user portal: signup, 2FA, reloads, invoices
|
||
- **[docs/DEVELOPER.md](docs/DEVELOPER.md)** — full developer documentation
|
||
- **[docs/WALKTHROUGH.md](docs/WALKTHROUGH.md)** — original step-by-step pricing walkthrough
|
||
|
||
## Surfaces
|
||
|
||
| Surface | URL | Audience |
|
||
|---|---|---|
|
||
| Public API | `/v1/*` | API customers (`x-api-key`) |
|
||
| Interactive API docs | `/docs` | Integrating developers |
|
||
| Admin console | `/admin` | Company ops & accounting |
|
||
| Customer portal | `/portal` | End-user customers (signup, 2FA, billing) |
|
||
| Zapier app | `zapier-app/` | No-code users via Zapier |
|
||
|
||
## Pricing model
|
||
|
||
`openapi.yaml` defines the API surface; each `operationId` is a rate-card key.
|
||
Endpoints carry **list prices** (seed: `src/pricing.ts` → `DEFAULT_RATE_CARD`).
|
||
Customer types are **tier configs** (`DEFAULT_TIERS`) with a `multiplier`, a
|
||
`monthlyCreditCents` quota, and an optional `defaultRule` for endpoints not on the card.
|
||
Individual customers can carry a `multiplierOverride`.
|
||
Billed price = `round(list price × multiplier)`; usage up to the monthly credit is free.
|
||
Pricing is editable at runtime in the admin console.
|
||
|
||
### Seed rate card (list prices, cents per call)
|
||
|
||
| Endpoint | Model | List price |
|
||
| -------------- | -------- | -------------------------------------------- |
|
||
| `status` | free | 0 |
|
||
| `storage-list` | free | 0 |
|
||
| `transform` | fixed | 4 |
|
||
| `storage` | variable | 10 + 1 per KB metadata + 50 per MB attached |
|
||
|
||
### Seed customer types
|
||
|
||
| Tier | Multiplier | Monthly credit | Default rule (unlisted endpoints) |
|
||
| ---------- | ---------- | -------------- | --------------------------------- |
|
||
| `free` | 1.0 | 100 cents | none — call rejected with 403 |
|
||
| `pro` | 0.5 | 1000 cents | fixed 8 list → 4 billed |
|
||
| `business` | 0.25 | 10000 cents | fixed 8 list → 2 billed |
|
||
|
||
Adding a new API call = add it to `openapi.yaml`, then price it in the admin UI.
|
||
Adding a customer type = create it in the admin UI. Variable pricing = base per call +
|
||
metadata size (rounded up to KB) + attachment size (rounded up to MB), then the multiplier.
|
||
|
||
## Quickstart
|
||
|
||
```sh
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
The server starts on port 3000. API docs at
|
||
[http://localhost:3000/docs](http://localhost:3000/docs), admin console at
|
||
`/admin`, customer portal at `/portal`.
|
||
|
||
## Deploying
|
||
|
||
```sh
|
||
npm ci && npm run build
|
||
node dist/index.js # runs from ANY working directory
|
||
```
|
||
|
||
All runtime paths (SQLite default, `.env`, OpenAPI spec, static assets)
|
||
resolve from the installation root, so the compiled server works under
|
||
systemd, Docker, or cron regardless of cwd. `PORT` and `ZAPPIER_DB` remain
|
||
environment-overridable.
|
||
|
||
## Environment variables
|
||
|
||
| Variable | Default | Purpose |
|
||
| ------------------- | -------------- | --------------------------------------------------- |
|
||
| `PORT` | `3000` | HTTP port the server listens on |
|
||
| `ZAPPIER_DB` | `<root>/zappier.db` | SQLite database file path |
|
||
| `ADMIN_KEY` | `admin-dev-key`| Admin UI / admin API key — **set a real secret in production** |
|
||
| `ADMIN_USER` | `admin` | Admin UI primary login username |
|
||
| `DEMO_ADMIN_USER` | `demo` | Admin UI demo login username |
|
||
| `DEMO_ADMIN_PASSWORD` | `$$$Adm1n###` | Demo login password — **override in production** |
|
||
| `STRIPE_SECRET_KEY` | _(none)_ | Stripe secret key — billing job and portal reloads |
|
||
|
||
## Billing
|
||
|
||
Usage is reported to Stripe by a job (loads `STRIPE_SECRET_KEY` from `.env`):
|
||
|
||
```sh
|
||
npx ts-node src/jobs/report-usage.ts
|
||
```
|
||
|
||
The job sums each customer's usage since the first of the current month (UTC),
|
||
applies the tier's monthly credit, and reports only the **delta** above what was
|
||
already reported — re-runs are safe. Idempotency comes from three layers: a
|
||
`billing_reports` ledger (cumulative cents per customer per month), an atomic
|
||
`job_locks` run guard (1 h TTL), and a deterministic Stripe event `identifier`
|
||
(`customer:period:billable`) that dedupes crash retries. It requires a Stripe
|
||
meter named `zappier.api_cents` with Sum aggregation over the `value` field.
|
||
|
||
A Kimi cron job ("Zappier billing · report usage to Stripe") runs it daily at
|
||
06:17 America/New_York with a completion notification.
|
||
|
||
Purchase-order customers are invoiced manually from the admin console
|
||
(**Invoices** tab); prepaid balances from the customer portal are drawn down
|
||
automatically at invoice issue. See `docs/ACCOUNTING.md`.
|
||
|
||
## Zapier app
|
||
|
||
The companion Zapier integration lives in `zapier-app/`:
|
||
|
||
```sh
|
||
cd zapier-app
|
||
npm install
|
||
npm test
|
||
```
|
||
|
||
To deploy it, create a Zapier developer account, run `zapier login`, then
|
||
`zapier push` from the `zapier-app/` directory.
|
||
|
||
## Testing
|
||
|
||
```sh
|
||
npm test # root API/service suite (jest, 165 tests)
|
||
cd zapier-app && npm test # Zapier integration suite (mocha, 4 tests)
|
||
```
|