# Getting started — Verae Time × Zapier **System architecture, functionality, integration guide, and next steps** This is the working document for the research workspace at `/Users/marchon/research/zapier`. It covers the full stack that connects Zapier automations to the Verae Timestamping Service, how to run and extend it locally, Zapier vs Verae billing, a client pattern for timestamped files on Peergos/IPFS with cold retrieve-on-demand, and what remains before a private Zapier listing. Grok (and humans) should start here, then follow the playbook in [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md). --- ## 1. How to start this workspace ```bash cd /Users/marchon/research/zapier ./scripts/restart-grok.sh # Mongo tunnel + grok --resume ``` Already in Grok: ``` /zapier-build ``` Mandatory first query (same playbook as [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md)): ```js db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" }) ``` Keep the Mongo tunnel up (`./scripts/ensure-mongo-tunnel.sh`). Check readiness with `./scripts/zapier-status.sh`. Load CLI PATH and env with `source scripts/dev-env.sh`. Leave the TUI with `/quit` (not `/new`). Restart details: [RESTART.md](RESTART.md). --- ## 2. System architecture ### 2.1 Purpose Connect Zapier automations to Verae blockchain timestamping **without**: - exposing raw Verae JWTs to Zapier end users - requiring Zapier to poll async jobs on `api.veraetime.net` - coupling billing and plan limits to the core timestamping API - running a multi-instance edge with only in-memory job queues ### 2.2 Bottom line ![High-level architecture: Zapier cloud, middleware, NATS workers, Verae API, and REST Hooks](docs/diagrams/01-high-level-architecture.svg) ```text Users → Zapier UI Zapier cloud runs the Platform CLI app (verae-zapier or scratch/veraetime) → HTTPS only → verae-zapier-middleware /zapier/v1/* → (sync) HTTPS → https://api.veraetime.net → (async) NATS JetStream → workers → HTTPS → api.veraetime.net (status poll) → HTTPS → hooks.zapier.com (REST Hook delivery) ``` - Zapier **never** connects to NATS. - Zapier **never** calls `api.veraetime.net` directly. - Middleware HTTP owns auth, tenancy, entitlements, metering, and the public API surface. - NATS owns durable job watching, completion events, and reliable webhook delivery (when `NATS_ENABLED=true`). - With `NATS_ENABLED=false`, an in-process poller still implements the same HTTP product path (Phase 6). ### 2.3 Components | Component | Runs where | Role | |-----------|------------|------| | **Zapier Platform CLI app** | Zapier cloud (when a Zap step runs) | Auth fields, map operations to `/zapier/v1/*`, attach Bearer token, translate 402/403 | | **verae-zapier-middleware** | Your infrastructure, public HTTPS | Tenant identity, API keys, Verae login bridge, entitlements, REST Hook storage, publish work to NATS | | **NATS + JetStream** | Private network with middleware | Work queues for job watch and webhook delivery; event stream for terminal job states | | **Workers** | Same deploy or separate processes | Job poller, event router, webhook deliverer | | **api.veraetime.net** | Verae production | Source of truth: login, timestamp jobs, status, verification | #### Zapier app (two copies in this repo) | Path | Language | Auth | Status | |------|----------|------|--------| | `verae-zapier-api/verae-zapier/` | JavaScript | Custom API key (`zmw_…`) | Phase 11 package; `MIDDLEWARE_BASE_URL` env | | `scratch/veraetime/` | TypeScript (CLI 19.1.0) | Session: username/password **or** API key → `accessToken` | Local golden connector; `build` + `validate` | Both talk **only** to middleware. The TypeScript app is the one this workspace validates day-to-day. The JavaScript app is the vendored product package from the middleware monorepo. **The Zapier app does not:** call `api.veraetime.net`, speak NATS, or enforce plan quotas. #### Middleware HTTP edge ![Middleware internals: public and protected /zapier/v1 routes, store, and flags](docs/diagrams/08-middleware-internals.svg) Path: `verae-zapier-api/verae-zapier-middleware/` - Express app: `GET /health`, mount `/zapier` - Public: `POST /zapier/v1/auth/login`, `GET /zapier/v1/auth/me`, tenant signup - Protected (Bearer or `x-api-key` + rate limit): timestamp, verify, status, webhooks - Admin: `/zapier/v1/admin/*` behind `X-Admin-Secret` - Store: file JSON MVP (`STORE_PATH`, default `./data/store.json`) — single-node only #### NATS + workers | Worker | Consumes | Calls | |--------|----------|-------| | Job poller | `verae.zapier.jobs.watch` | `GET /api/status/{jobId}` on Verae | | Event router | `verae.zapier.jobs.events` | Enqueues webhook deliveries | | Webhook deliver | `verae.zapier.webhooks.deliver` | `POST` Zapier `targetUrl` | Streams: `ZAPIER_JOBS`, `ZAPIER_EVENTS`, `ZAPIER_WEBHOOKS` (optional `ZAPIER_USAGE`). #### Verae Timestamping Service - Swagger UI: https://api.veraetime.net/docs/swagger/index.html - OpenAPI: https://api.veraetime.net/docs/swagger/openapi.yaml - Local copy: [scratch/our-api/openapi.yaml](scratch/our-api/openapi.yaml) - Auth: `POST /auth/login` → JWT in `token`; all other API routes `Authorization: Bearer ` - Async create: `POST /api/timestamp` → **202** `{ jobId }` ### 2.4 Security boundaries ![Security boundaries: public HTTPS versus private NATS, store, and Verae JWT](docs/diagrams/02-security-boundaries.svg) ```text Public Internet ├─ Zapier cloud → Middleware HTTPS only └─ Middleware → Zapier REST Hook HTTPS only Private ├─ Middleware ↔ NATS (never expose NATS ports) └─ Middleware / workers → api.veraetime.net HTTPS ``` Rules: - Never put raw Verae JWTs in NATS when a `tokenRef` will do. - Never expose NATS (`4222`) to the public internet. - Debug logs must redact `Bearer`, `zmw_`, `zmt_`, passwords, and hook query secrets. - Treat `targetUrl` as untrusted egress (timeouts; SSRF allowlist is Phase 15). - Do not commit `~/.zapierrc`, `.env`, `store.json`, or `~/.mcp-env`. ### 2.5 Scaling model - **HTTP edge:** stateless replicas behind a load balancer, **except** they share a store. File JSON is single-node. Multi-node needs Postgres/Redis (Phase 15). - **NATS consumers:** queue groups — more workers increase poll/deliver throughput; two workers must not double-complete the same job. - **Rollback:** `NATS_ENABLED=false` still serves the full HTTP API with the in-process poller. --- ## 3. Functionality ### 3.1 Authentication (two hops) ![Two-hop authentication: user to middleware tokens; middleware to Verae JWT](docs/diagrams/03-auth-two-hop.svg) **End user → middleware** | Mode | What the user enters | What happens | |------|----------------------|--------------| | Session (TypeScript app) | Username + password, **or** middleware API key | `POST /zapier/v1/auth/login` → `{ accessToken }` stored as `sessionKey` | | Custom key (JS app) | Tenant API key `zmw_…` | Sent as `Authorization: Bearer` on every request; test is `GET /zapier/v1/auth/me` | Connection test: `GET /zapier/v1/auth/me` → tenant, plan, usage. Connection label: username/plan (TS) or `tenantId (plan)` (JS). 401 on later calls: TypeScript app throws `z.errors.RefreshAuthError` so Zapier re-runs session `perform`. **Middleware → Verae** Middleware logs into Verae with the tenant’s Verae credentials (or mock client when `MOCK_VERAE=true`) and holds the JWT **server-side**. Zapier never sees that JWT. Token types issued by middleware: | Prefix | Kind | |--------|------| | `zmw_` | Tenant API key (long-lived) | | `zmt_` | Middleware session token (HMAC, `TOKEN_SECRET`) | ### 3.2 Zapier operations ↔ middleware ↔ Verae ![Operations map: Zapier nouns to /zapier/v1 to Verae](docs/diagrams/07-operations-map.svg) All Zapier routes are under `/zapier/v1`. Connector default base: `http://127.0.0.1:3100`. Production base is the **deployed middleware**, not `api.veraetime.net`. | Zapier noun | Type | Middleware | Verae (what middleware wraps) | Notes | |-------------|------|------------|-------------------------------|-------| | Create Timestamp | create | `POST /timestamp` | `POST /api/timestamp` | Returns `{ jobId }` (202). Use with the hook trigger. | | Create Timestamp and Wait | create | `POST /timestamp/wait` | create + poll/wait | Returns terminal status, or pending + `jobId` on timeout. | | Create Batch Timestamps | create | `POST /timestamp/batch` | `POST /api/batch/timestamp` | `{ items: [{ data, hashAlg? }] }` | | Verify Certificate | create | `POST /verify` | `POST /api/verify` | `{ certificate }` → `{ valid, timestamp, blockIndex }` | | Find Job Status | search | `GET /status/{jobId}` | `GET /api/status/{jobId}` | Search: empty array if 404. | | Find Job Verification | search (TS only) | `GET /status/{jobId}/verification` | `GET /api/verify/{jobId}` | Search. | | Timestamp Completed | hook trigger | `POST /webhooks/subscribe` · `DELETE /webhooks/unsubscribe` | (no Verae hook) | Event `timestamp.completed`; Zapier supplies `targetUrl`. | Not in v1 (admin HTML / user admin on Verae): dashboard, metrics, queue UI, user CRUD, batch verify/status, get-block-by-hash. Add later if a Zap needs them. Create input (timestamp): required `data` (text), optional `hashAlg` (default SHA256). ### 3.3 Request flows **A. Create Timestamp (async + hook)** ![Create Timestamp sequence: async job plus REST Hook](docs/diagrams/04-flow-async-timestamp.svg) ```text Zapier → POST /zapier/v1/timestamp Middleware: authenticate, checkEntitlement, POST /api/timestamp Middleware: publish jobs.watch → return 202 { jobId } Worker: poll GET /api/status/{jobId} until terminal → publish jobs.events Event router: match webhooks → publish webhooks.deliver Webhook worker: POST hooks.zapier.com/... (timestamp.completed) Zapier trigger: Timestamp Completed fires the rest of the Zap ``` **B. Create Timestamp and Wait** ![Create Timestamp and Wait: in-process path versus Phase 9 NATS](docs/diagrams/05-flow-wait.svg) ```text Zapier → POST /zapier/v1/timestamp/wait Middleware: create + wait (in-process if NATS off; NATS events when Phase 9 lands) → StatusResponse (completed/failed) or { jobId, status: "pending" } on timeout ``` Today, wait works on the in-process path (`NATS_ENABLED=false`). Wait-via-NATS is **Phase 9 (open)**. **C. Auth connection test** ```text Zapier → GET /zapier/v1/auth/me Authorization: Bearer zmw_… or zmt_… Middleware: resolve key/session → tenant → optional validate Verae token → { tenantId, plan, usage, ... } ``` ### 3.4 NATS subjects (private) ![NATS topology: streams, subjects, consumers, and ack rules](docs/diagrams/06-nats-topology.svg) | Subject | Publisher | Consumer | Payload gist | |---------|-----------|----------|--------------| | `verae.zapier.jobs.watch` | HTTP edge after create | `job-poller` (queue) | `tenantId`, `jobId`, `tokenRef`, attempts, `traceId` | | `verae.zapier.jobs.events` | Job poller (terminal) | Event router; optional HTTP waiters | `timestamp.completed\|failed\|timeout`, status object | | `verae.zapier.webhooks.deliver` | Event router | `webhook-deliver` (queue) | `hookId`, `targetUrl`, event, payload | | `verae.zapier.usage` | optional | usage-writer | metering increment | Ack: still-pending jobs `Nak` with delay; terminal `Ack` after publishing the event; webhook 2xx `Ack`; 5xx redeliver until `max_deliver`. ### 3.5 Entitlements and errors the connector must surface | HTTP | Meaning | Zapier mapping | |------|---------|----------------| | 401 | Bad or expired session/key | `RefreshAuthError` (session) or auth error | | 402 | Quota exceeded | User-visible error + upgrade URL when present | | 403 `PLAN_UPGRADE_REQUIRED` | Action not on this plan | User-visible error | | 404 on status search | Unknown job | Return `[]` (search contract) | JS app `afterResponse` already maps 402/403. TypeScript app currently remaps 401 only — 402/403 mapping is a next-step item. ### 3.6 Feature flags and environment | Variable | Default | Purpose | |----------|---------|---------| | `PORT` | `3100` | Middleware listen port | | `VERAE_API_BASE_URL` | `http://localhost:8080` | Upstream Verae (prod: `https://api.veraetime.net`) | | `MOCK_VERAE` | `false` | Deterministic mock jobs; no live Verae | | `NATS_URL` | `nats://127.0.0.1:4222` | NATS server | | `NATS_ENABLED` | `false` | JetStream workers vs in-process poller | | `TOKEN_SECRET` | dev secret | HMAC for `zmt_` session tokens | | `DEBUG_VERAE` | unset | Namespaces: `auth`, `nats`, `jobs`, `webhooks`, `http`, `billing` (or `1` for all) | | `DEBUG_VERAE_LEVEL` | `debug` | `debug` \| `info` \| `warn` \| `error` | | `STORE_PATH` | `./data/store.json` | MVP tenant/usage/webhook store | | `MIDDLEWARE_BASE_URL` | `http://127.0.0.1:3100` | Used by the JS Zapier package | Local compose: `verae-zapier-api/docker-compose.yml` (NATS + middleware). Middleware can run without NATS when the flag is off. --- ## 4. Integration guide ### 4.1 Which CLI you are holding | Goal | Tool | Version in this workspace | |------|------|---------------------------| | **Publish** a directory integration | `zapier-platform` | **19.1.0** (`~/.npm-global/bin`) | | **Consume** existing Zapier apps from code | `zapier-sdk` | **0.77.1** | | AI client over the 9k catalog | Hosted MCP `https://mcp.zapier.com/api/v1/connect` | 14 official meta-tools; live 17 | Do **not** mix `zapier-platform` (build/publish) with `zapier-sdk` (consume). Do **not** recommend retired NLA / AI Actions. Do **not** invent `selected_api` ids or action keys. ### 4.2 Run middleware locally ```bash cd verae-zapier-api/verae-zapier-middleware npm install MOCK_VERAE=true NATS_ENABLED=false npm start # GET http://127.0.0.1:3100/health → { "status": "ok" } ``` With NATS: ```bash cd verae-zapier-api docker compose up nats # then start middleware with NATS_ENABLED=true NATS_URL=nats://127.0.0.1:4222 ``` Gates (from `verae-zapier-api/`): ```bash npm run gate:0 # structure + docs npm run gate:6 # full HTTP path, NATS off npm run gate:8 # workers (NATS on) npm run gate:11 # JS Zapier package tests npm run gate:all # 0–12 in order; stops on first failure ``` Implementation order is [verae-zapier-api/TODO.md](verae-zapier-api/TODO.md). Do not skip gates. ### 4.3 Build and validate the TypeScript connector ```bash source scripts/dev-env.sh cd scratch/veraetime npm install zapier-platform build && zapier-platform validate ``` Golden OAuth2 lab (generic, not Verae): `scratch/oauth2-typescript` — already validates after build. Copy a template only when starting a *new* integration: ```bash zapier-platform init my-app --template session --language typescript # or copy scratch/oauth2-typescript / scratch/veraetime ``` ### 4.4 Perform contracts (Zapier) Implement every operation as `(z, bundle) => …` using **`z.request` only**. - **Triggers and searches** return **arrays of objects**. Polling items need a stable `id`. - **Creates** return **one object**. - Refreshable 401 → `throw new z.errors.RefreshAuthError()`. - Hook triggers implement `performSubscribe` / `performUnsubscribe` / `perform` / `performList` (sample for the editor). - Do not call Verae or NATS from the app. ### 4.5 Login and publish (blocked until you authenticate) There is no `~/.zapierrc` until you finish a browser login. Local `validate` works. `register` and `push` do not. ```bash source scripts/dev-env.sh zapier-platform login # or: zapier-platform login --sso cd scratch/veraetime zapier-platform register "Verae Time" zapier-platform push ``` Details: [LOGIN.md](LOGIN.md). Never commit the deploy key. Production Zapier cloud **cannot** reach `http://127.0.0.1:3100`. Before a real Zap you need a public HTTPS middleware URL and `api_base_url` / `MIDDLEWARE_BASE_URL` pointed at it (Phase 13–14). ### 4.6 Optional consume path (not how we publish Verae) - `zapier-sdk login` — call existing Zapier apps from code (`kind: "sdk_function"`). - Hosted Zapier MCP — discover → enable → inspect → execute. Writes need explicit user approval. Successful executes cost **2 Zapier tasks**. See [MCP-REFERENCE.md](MCP-REFERENCE.md). ### 4.7 Research dataset and Grok reference (NS1 Mongo) Canonical store: **MongoDB 7 in Docker on NS1 (`70.88.205.138`)**, bound to **127.0.0.1:27017 only**. Connect only through the SSH tunnel: ```bash ./scripts/mongo-tunnel.sh # ssh -N -L 27017:127.0.0.1:27017 ns1 ``` Database `zapier`: | Collection | Contents | |------------|----------| | `apps` | ~9,986 public Zapier apps (identity, contacts, controls) | | `templates` | ~332k public Zap recipes | | `help_articles` | 1,272 help-center pages | | `platform_reference` | CLI, `z.*`, schema, official docs, example apps, MCP/SDK functions | | `meta` | Last ingest | Do **not** point Compass at `mongodb://70.88.205.138:27017`. Credentials live in `~/.mcp-env` (mode 600), not git. Full notes: [MONGO.md](MONGO.md). Useful queries: ```js db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" }) db.platform_reference.find({ kind: "core_function", key: "z.request" }) db.platform_reference.find({ kind: "cli_function", key: "init" }) db.platform_reference.find({ kind: "template", key: "session-auth" }) db.platform_reference.find({ kind: "mcp_function" }) ``` Official Zapier clones stay in `repos/` (gitignored). Refresh with `./scripts/clone-zapier-repos.sh`. --- ## 5. Workspace map ![Workspace: repo, CLIs, Mongo tunnel, publish versus consume](docs/diagrams/10-workspace-integration.svg) | Path | Use | |------|-----| | [getting-started.md](getting-started.md) / [getting-started.pdf](getting-started.pdf) | This architecture + integration guide | | [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md) | Routing table for all Zapier work | | [FUNCTIONS-REFERENCE.md](FUNCTIONS-REFERENCE.md) | Every CLI / `z.*` / SDK function | | [MCP-REFERENCE.md](MCP-REFERENCE.md) | Hosted MCP meta-tools | | [LOGIN.md](LOGIN.md) | Browser login for platform + SDK | | [RESTART.md](RESTART.md) | Quit and resume this session | | [MONGO.md](MONGO.md) | NS1 Mongo, tunnel, collections | | `.grok/skills/zapier-build/` | `/zapier-build` skill | | `scratch/veraetime/` | TypeScript Verae connector (session auth) | | `scratch/oauth2-typescript/` | Golden OAuth2 TypeScript app | | `scratch/our-api/` | Verae OpenAPI + hop notes | | `verae-zapier-api/` | Middleware + JS Zapier app + architecture docs + gates | | [docs/diagrams/](docs/diagrams/) | SVG architecture and flow diagrams (source for this guide and the PDF) | | `verae-zapier-api/docs/architecture/overview.md` | Component diagram (source of truth for the edge) | | `verae-zapier-api/docs/architecture/nats-subjects.md` | Subjects, streams, payloads | | `verae-zapier-api/docs/api/middleware-openapi.yaml` | Zapier-facing OpenAPI | | `verae-zapier-api/TODO.md` | Phased plan with test gates | | `scripts/dev-env.sh` | PATH + env | | `scripts/ensure-mongo-tunnel.sh` / `mongo-tunnel.sh` | NS1 tunnel | | `scripts/restart-grok.sh` / `zapier-status.sh` | Session + readiness | | `repos/` | Official Zapier clones (local only) | --- ## 6. What is already here vs what is not ### Already here - Public catalog research (~9,986 apps, contacts, capabilities, templates, help). - Platform reference in Mongo + on disk; `/zapier-build` skill. - Platform CLI 19.1.0 and SDK CLI 0.77.1 on `PATH` via `dev-env.sh`. - Golden apps that **validate locally** without a Zapier login. - Verae OpenAPI ingested (`scratch/our-api/openapi.yaml`). - Full middleware source: auth, tenants, entitlements, timestamp/verify/status/webhooks, mock Verae, NATS publishers, workers, debug redaction. - Two Zapier app implementations wired to `/zapier/v1`. - Gates **0–8, 10, 11** recorded as passed in `TODO.md` (as of 2026-08-11 on the original monorepo). ### Not here yet 1. **Zapier developer login** — no `~/.zapierrc`; cannot `register` / `push`. 2. **Public HTTPS middleware** — Zapier cloud cannot hit localhost. 3. **Live invoke** of `scratch/veraetime` against middleware + real or mock Verae in this workspace (validate is schema-only). 4. **Phase 9** — `/timestamp/wait` subscribed to NATS events (multi-instance wait). 5. **Phase 12** — compose E2E smoke (health + auth + wait + webhook) as a gate. 6. **Phase 13** — production `VERAE_API_BASE_URL`, dedicated Zapier service user, secrets, TLS. 7. **Phase 14** — private push + human Zap (Drive/Sheets → Timestamp → Slack). 8. **Phase 15** — Postgres/Redis store, NATS mTLS, webhook SSRF allowlist. 9. **TS connector polish** — map 402/403 like the JS app; optional polling admin trigger for `/admin/timestamps`. 10. **Grok MCP handshake** — Mongo MCP and chrome-bridge need a healthy tunnel / Chrome Connect after restart (`/mcps`). 11. **Peergos / pin / Glacier retrieval service** — proposed in §10; not a v1 connector operation. This repo has the Zapier **platform** docs and the **Verae** OpenAPI. It still cannot invent new Verae endpoints. If a Zap needs an admin route that is not in the v1 table, add middleware + connector operations from the OpenAPI — do not guess. --- ## 7. Next steps (recommended order) ![Phase roadmap: gates 0–15, Phase 9 open](docs/diagrams/09-phase-roadmap.svg) Work the product path in this order. Parallelism is only safe where `TODO.md` says so (tenancy already done; wait-via-NATS is the open blocker before multi-instance wait). ### Now — local integration (this workspace) 1. Start middleware with `MOCK_VERAE=true` and `NATS_ENABLED=false`. 2. Create a free tenant (`POST /zapier/v1/signup` or seed script) and confirm `GET /health` + `GET /zapier/v1/auth/me`. 3. `zapier-platform build && zapier-platform validate` in `scratch/veraetime`. 4. After `zapier-platform login`, `zapier-platform invoke auth test` and invoke create/search against local middleware (`--debug`). 5. Map 402/403 in `scratch/veraetime/src/middleware.ts` to match the JS app. ### Next — close middleware gaps 6. **Phase 9:** wait on `verae.zapier.jobs.events` with a hard timeout (`pending` + `jobId`). 7. **Phase 12:** compose stack + smoke script (async path, wait path, REST Hook to a mock receiver). 8. Confirm `NATS_ENABLED=true` and `false` both still pass their gates. ### Then — production and private listing 9. **Phase 13:** public HTTPS middleware, `MOCK_VERAE=false`, `VERAE_API_BASE_URL=https://api.veraetime.net`, dedicated Verae service user, managed secrets. 10. Point the connector `api_base_url` at that origin. Zapier cloud must reach it. 11. **Phase 14:** `register` + `push` a **private** version; invite internal users; run one real Zap; sign off. 12. Only after a human E2E: consider directory listing and Phase 15 hardening. ### Ongoing — research / Grok 13. Keep `./scripts/ensure-mongo-tunnel.sh` running; after restart hit `/mcps` and refresh **mongodb**. 14. Re-ingest `platform_reference` on NS1 after recloning official repos. 15. Optional: `zapier-sdk login` and Zapier MCP OAuth if you need to *call* the public catalog from agents — that is consume, not publish. --- ## 8. Hard rules - Start Zapier coding from `guide/build-new-connector` or [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md). - Connector → middleware `/zapier/v1` only. Never `api.veraetime.net` from Zapier. Never NATS from Zapier. - Use `z.request` in performs. Triggers/searches return arrays; creates return one object. - `zapier-platform` publishes; `zapier-sdk` / hosted MCP consume. Do not mix. - Do not recommend retired NLA / AI Actions. - Do not invent vendor APIs, auth schemes, or Zapier `selected_api` keys. - Mongo on NS1 is localhost-only; always tunnel. - No secrets in git or in `DEBUG_VERAE` output. --- ## 9. Zapier billing models Zapier and Verae bill **separately**. A client Zap that timestamps a file pays Zapier for successful **tasks** and pays Verae (via middleware entitlements) for **timestamp / verify** operations. Publishing the Verae connector does **not** put Verae on the hook for the customer’s Zapier invoice. Figures below are **USD, August 2026**, from [zapier.com/pricing](https://zapier.com/pricing). Annual prices are the discounted per-month equivalent. Confirm live rates before quoting a customer. ![Zapier and Verae billing layers](docs/diagrams/11-zapier-billing.svg) ### 9.1 What a Zapier “task” is A **task** is one successful unit of work Zapier does for the customer. Failed steps are free. | Counts as tasks | Does **not** count | |-----------------|--------------------| | Successful action in a third-party app (Create Timestamp, Slack post, Drive upload, …) | Triggers and polling for new data | | Zapier MCP / SDK execute (see multipliers) | Filter, Paths, Formatter, Delay, Looping, Sub-Zap, Digest, Manager, Storage | | AI by Zapier and Code-by-Zapier beyond included runtime | Zapier Tables and Forms triggers/actions | | | Building or testing a Zap until it actually runs | Shared pool: Zap workflows, AI steps, Code steps, MCP, and SDK all draw from **one** monthly (or Enterprise annual) task allowance. There is no separate MCP budget. **Multipliers** (official rate card; confirm [zapier.com/pricing/rates](https://zapier.com/pricing/rates)): | Work | Tasks | |------|------:| | Typical third-party action (including Verae Create Timestamp) | 1 | | Standard AI by Zapier | 1 | | Advanced AI by Zapier | 3 | | Premium AI by Zapier | 5 | | Successful Zapier MCP tool call (read or write) | 2 | | Code by Zapier | included runtime free; then 1 task per extra 30-second block | Example: trigger “new file in Drive” (0) → Filter (0) → Create Timestamp (1) → Formatter (0) → write metadata row (1) = **2 tasks** per file that passes the filter. ### 9.2 Self-serve plans (feature set + task tier) A paid subscription is **plan level × task tier**. | Plan | Seats | Workflows | Polling | Entry task tier (annual) | Standout | |------|------:|-----------|---------|--------------------------|----------| | **Free** | 1 | Two-step only | 15 min | 100 tasks / month, $0 | Try automation; no pay-per-task overflow | | **Professional** | 1 | Multi-step; premium apps; webhooks | 2 min | 750 tasks from **$19.99**/mo annual ($29.99 monthly) | Filters, Paths, Formatter, AI by Zapier, Autoreplay | | **Team** | 25 | Same as Pro | 1 min | 2,000 tasks from **$69**/mo annual ($103.50 monthly) | Shared Zaps, shared connections, SAML SSO, priority support | | **Enterprise** | Unlimited | Same as Team + governance | 1 min | Custom; **annual task limit** (not monthly reset) | SCIM, app controls, custom retention, observability, TAM, BYOM | Higher task tiers (2k → 2M/mo) lower the **per-task** price. Team starts at 2,000. Volumes above 2M go through Sales. 14-day Professional trial (no card). Non-profit: extra 15% off (not on pay-per-task). ### 9.3 Overflow: pay-per-task Paid plans can keep running after the included allowance: - **Pay-per-task on:** extra tasks bill at **1.25×** the plan’s base task rate (annual) or **2.5×** (monthly). Ceiling is **3×** the subscribed tasks, then Zaps pause until the next cycle or an upgrade. - **Pay-per-task off:** usage **stops** at the allowance. - Free has no overflow. Enterprise uses an annual pool instead of a monthly reset. ### 9.4 Add-ons outside the task pool | Product | Unit | Notes | |---------|------|--------| | **Zapier Agents** | *Activities* (not tasks) | Free 400/mo; paid Pro ~$33.33/mo annual for 1,500. Does not consume Zap tasks. | | **Zapier Chatbots** | Feature tiers (count of bots) | Free includes 2; paid adds more. Not usage-metered on tasks. | ### 9.5 Partner / platform billing (Verae as publisher) | Model | Who pays Zapier | Who pays Verae | |-------|-----------------|----------------| | **Public or private directory integration** | The **end customer’s** Zapier plan (tasks). Publishing is **free**; Zapier does not bill the partner for usage of their app. | The customer’s Verae tenant (middleware plan / API key). | | **Zapier MCP / SDK (consume)** | Same customer task pool (MCP execute = 2 tasks). SDK is **free in beta**; Zapier will announce when beta pricing starts. | Only if the MCP/SDK action hits Verae. | | **Powered by Zapier / White Label / embed** | Usually the **product company** (usage-based). End users authorize apps in *your* UI; Zapier has said end users need not have their own Zapier bill for background runs. Contract with Zapier Sales. | Verae bills the product company or the tenant, depending on how you provision keys. | | **Retired NLA / AI Actions** | Do not sell or design around this. | — | Verae should not promise “unlimited Zapier” or absorb a customer’s Zapier invoice unless a White Label contract says so. ### 9.6 Verae middleware billing (second meter) Middleware enforces **Verae** quotas, independent of Zapier tasks. Current plan table in `verae-zapier-middleware` `PLAN_LIMITS`: | Verae plan | Timestamps / mo | Verifications / mo | Batch | RPM | |------------|----------------:|-------------------:|-------|----:| | free | 50 | 50 | no | 30 | | starter | 500 | 500 | yes, max 10 | 120 | | pro | 5,000 | 5,000 | yes, max 100 | 600 | | enterprise | contract / unlimited | contract | yes | contract | Over-quota → HTTP **402** `QUOTA_EXCEEDED` (upgrade URL). Wrong plan for batch → **403** `PLAN_UPGRADE_REQUIRED`. Those errors must surface in the Zap; they are not Zapier task overages. A client therefore sees **two invoices**: Zapier (tasks) and Verae (timestamps). Design Zaps so a 402 does not retry in a tight loop (that still burns Zapier tasks on each failed? — failed actions are **not** Zapier tasks, but Autoreplay/retries can still hammer Verae). ### 9.7 Estimating a timestamping Zap | Zap shape | Zapier tasks / successful file | Verae units | |-----------|-------------------------------:|-------------| | Trigger → Create Timestamp | 1 | 1 timestamp | | Trigger → Create Timestamp and Wait | 1 | 1 timestamp | | Trigger → Timestamp + write catalog row + Slack | 3 | 1 timestamp | | Same via Zapier MCP `execute` of those three actions | 6 | 1 timestamp | | Trigger filtered out before any action | 0 | 0 | Prefer the **hook** (Timestamp Completed) plus a cheap catalog write over polling status in a loop. --- ## 10. Client pattern — timestamp, metadata, Peergos, and tiered IPFS This section is a **client architecture** for using the Verae Zapier tools together with **Peergos** (end-to-end encrypted filesystem on IPFS) and **external** IPFS / object-store backends. It is **not** implemented as connector operations today. Do not invent Peergos or AWS APIs in the Zapier app; add middleware routes only when those APIs exist and are documented. Goal: clients can (1) timestamp content, (2) append that proof to metadata, (3) store the file in Peergos, (4) pin a **hot cache** on one or more IPFS providers, (5) migrate bytes to **long-term low-cost** object storage (Amazon S3 Glacier family, or an Apache Iceberg catalog on S3), and (6) **rehydrate on demand** when an IPFS request hits an index — without keeping every file live on a gateway 24/7. ![Timestamped files: Peergos, pin cache, and cold retrieve-on-demand](docs/diagrams/12-peergos-ipfs-tiered-storage.svg) ### 10.1 What each layer is for | Layer | Role | Stays hot? | |-------|------|------------| | **Zapier + Verae connector** | Orchestrate: hash → timestamp → write metadata; notify on `timestamp.completed` | n/a (control plane) | | **Verae Time** | Blockchain timestamp of a payload (typically a **content hash** or a metadata JSON, not the raw file bytes) | Proof is small; keep forever | | **Metadata / index** | Maps `cid` → `jobId`, Verae certificate fields, Peergos path, storage class, restore handle | **Yes** — this is the only always-on map | | **Peergos** | User-owned, e2e-encrypted filesystem on IPFS/libp2p. Host cannot read file or most metadata. Sharing and apps stay in the user’s graph. | User’s chosen Peergos host; not a public CDN | | **Cached pin (hot IPFS)** | Pinning service or your Kubo cluster (Pinata, Filebase, web3.storage, self-hosted). Serves frequent `ipfs get` / gateway hits. | **Only for a TTL or working set** | | **Cold object store** | Amazon **S3 Glacier Instant Retrieval**, **Flexible Retrieval**, or **Deep Archive**; or another cheap archive (Filecoin deal, Storj, Backblaze B2 + lifecycle). Optional **Apache Iceberg** table on S3 as the *catalog* of CID → bucket/key → storage class → restore job. | **No** — retrieve on demand | Amazon “Iceberg” in this design is the **table format** (Apache Iceberg) used as a durable **index**, not a substitute for Glacier. Long-term **bytes** live in Glacier-class (or equivalent) object storage. The user-facing name “Iceberg” is easy to mix with Glacier; keep the two distinct in customer docs. ### 10.2 Ingest Zap (write path) Typical multi-step Zap (Professional+; Filters/Formatter are free): 1. **Trigger** — new file in Drive, Dropbox, email, or a Peergos outbox / webhook. (0 Zapier tasks) 2. **Hash** — SHA-256 (or the hashAlg Verae accepts) of the bytes, or of the canonical metadata envelope. Prefer hashing **ciphertext** if the file is already encrypted for Peergos, so the timestamp commits to what is stored. 3. **Create Timestamp** (or Create and Wait) — `data` = hash or compact JSON `{ cid?, sha256, size, mime, source }`. (1 Zapier task, 1 Verae timestamp) 4. **Store file in Peergos** — user or service account writes the file into `/cubes/…` or a shared folder via a **documented** Peergos API or a future middleware route. Peergos assigns / retains an IPFS **CID** for the blocks. 5. **Hot pin (optional)** — Pinning Services API (or Filebase/Pinata) pins that CID for a cache TTL (hours–days), not forever. 6. **Append metadata** — write one index record (Zapier Tables is **free**; or Sheets/Postgres/Iceberg): ```text cid # IPFS content id (or Peergos block root) sha256 # digest that Verae timestamped veraeJobId veraeStatus # pending | completed | failed certificateRef # verify payload / block index when complete peergosPath # /home/… or /cubes//… storageClass # pin-hot | peergos-only | glacier-ir | glacier-deep coldBucket / key # S3 (or other) locator; empty until migrated restoreId # last Glacier restore job, if any pinnedUntil # when hot pin may be dropped createdAt ``` 7. **Timestamp Completed** hook — when middleware finishes the job, update the same row with certificate fields and notify Slack/email. (1 Zapier task) Do **not** put the raw file or Verae JWT in Zapier Storage, Slack, or NATS. ### 10.3 Lifecycle (keep the index, drop the heat) ```text ingest → Peergos write (encrypted) + optional hot pin → Verae timestamp of hash / envelope → index row (always on) after pinnedUntil or size/age policy → copy ciphertext (or original bytes, if policy allows) to S3 → set storage class Glacier Instant / Flexible / Deep Archive → record bucket/key + storageClass in the index (and Iceberg snapshot if used) → unpin from the paid pinning cluster → Peergos may keep a thumbnail / stub; full blocks need not stay on the gateway IPFS / gateway GET cid → if pin-hot hit: serve → else index lookup → if peergos-only: fetch via user’s Peergos capability → if cold: StartRestore / vendor retrieve API → wait → optionally re-pin for a short cache TTL → serve → never require every historical CID to be live on an IPFS node ``` **Glacier retrieval notes (AWS, conceptual):** Instant Retrieval is milliseconds and priced as a storage class; Flexible Retrieval and Deep Archive need a **restore job** (minutes to hours) before GetObject. The index must store enough to call the restore API (`bucket`, `key`, `versionId`, `restoreId`). Map IPFS requests to that restore; return `202` + `Retry-After` to the gateway until the object is hydrated. **Apache Iceberg** (optional): partition the catalog by `storageClass` and date so you can expire pin rows and audit restores with SQL, without loading every object. Iceberg does not store the file bytes. ### 10.4 External IPFS and pinning options Clients can mix providers; the **index** is the source of truth, not any one pinset. | Backend | Typical use | |---------|-------------| | Self-hosted Kubo / cluster | Hot working set you control | | Pinata, Filebase, web3.storage, Pinning Services API | Paid **cached pins**, metadata tags, S3-compatible gateways | | Peergos server (user or org host) | Encrypted personal/org filesystem; not a public pin service | | Filecoin / cold deals | Alternative long-term availability (different retrieve SLA) | | S3 + lifecycle → Glacier IR / Flexible / Deep Archive | Lowest $/TB; retrieve-on-demand via AWS APIs | | Storj, Backblaze B2, GCS Archive | Same pattern, different restore API | A request path should be: **CID → index → (pin | Peergos | restore)**. Do not walk every pin provider on every miss. ### 10.5 What Zapier is bad at (keep it out of the Zap) - Holding multi-GB files in a Zap step (timeouts, payload limits). Hash and pass **references** (Drive id, Peergos path, CID). - Being the IPFS gateway. Use a small **retrieval service** (your infra) that reads the index and talks to Peergos / pin / Glacier. - Polling Glacier restore every second (burns tasks). Use a webhook, queue, or “Find Job Status”-style search on a timer Zap with a Filter. ### 10.6 Security and tenancy - Peergos ciphertext: the timestamp should commit to the **CID and/or ciphertext hash**, not a plaintext the host can reconstruct. - Capabilities / sharing stay in Peergos; the public index should not leak readable paths or unencrypted names if the threat model forbids it (store opaque ids). - Middleware still never exposes Verae JWTs. Retrieval workers use **tenant-scoped** cloud credentials, not the Zapier connection. - 402/403 from Verae must not be “fixed” by falling back to an unauthenticated public pin of customer data. ### 10.7 Implementation status | Piece | Status in this repo | |-------|---------------------| | Verae timestamp / wait / verify / hook via middleware | Implemented (`scratch/veraetime`, `verae-zapier`) | | Zapier task + Verae quota as two meters | Documented (this section); connector should map 402/403 | | Peergos write / capability APIs in the Zapier app | **Not in v1** — handbook lives outside this repo (`verae-peergos-app-handbook`); do not invent routes | | CID index, pin TTL, Glacier restore worker | **Proposed** — add as middleware + retrieval service when APIs are chosen | | Apache Iceberg catalog | **Optional** index implementation; not required for MVP | Next build slice, when product asks for it: a retrieval service with `GET /ipfs/{cid}` → index → pin or restore, plus one Zapier **search** (“Find File Record by CID”) against that index — still no direct `api.veraetime.net` from Zapier.