# Endpoint I/O Each product NATS address: **what it is for**, **who sends**, **who receives**, **how to attach**. Connect with a **long-lived** client (reconnect-per-message is the anti-pattern): ```text NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 ``` Public edge (token in `/etc/nats/public-edge.token` on NS1): `nats://@70.88.205.138:4222`. Do not put that URL in Zapier or in Verae. --- ## `verae.zapier.jobs.watch` | | | |--|--| | For | Work queue: “poll Verae until this `jobId` is terminal.” | | In from | middleware `enqueueWatch` after `POST /api/timestamp` (or mock) returns `jobId` | | Out to | job-poller worker (queue `job-poller`, durable on `ZAPIER_JOBS`) | | Then | poller calls Verae `GET /api/status/{jobId}` over **HTTPS**, not NATS | Payload: `tenantId`, `veraeUserId`, `jobId`, `tokenRef`, `enqueuedAt`, `attempt`, `maxAttempts`, `intervalMs`, `traceId`. JWT stays in middleware (`tokenRef`); never on Zapier. **Connect Verae-side:** do **not** subscribe this from `api.veraetime.net`. The poller is the Verae HTTP client. To add another watcher, start another `job-poller` consumer in the same queue group. --- ## `verae.zapier.jobs.events` | | | |--|--| | For | Terminal outcomes: `timestamp.completed` / `failed` / `timeout` | | In from | job-poller after Verae status is not `pending` | | Out to | (1) middleware `waitForJobEvent` (wait HTTP); (2) webhook router → `webhooks.deliver`; (3) optional `verae-jobs-events` mailbox `:3030` | If `JOBS_EVENTS_EXCLUSIVE=1`, only `verae-jobs-events` is the durable consumer and republishes `verae.internal.jobs.events`. Default lab: middleware wait + webhook worker share the stream. Payload: `event`, `tenantId`, `jobId`, `status`, `traceId`, `emittedAt`. --- ## `verae.zapier.webhooks.deliver` | | | |--|--| | For | HTTPS POST to `hooks.zapier.com` (REST Hook) | | In from | webhook worker on completed/failed events | | Out to | Zapier cloud **HTTPS**, never NATS | Payload: `hookId`, `tenantId`, `targetUrl`, `event`, `payload`, `attempt`, `traceId`. Slow consumer: keep replica floors on the worker if you split it out of middleware. --- ## `verae.zapier.usage` | | | |--|--| | For | Optional meter of Zap actions on NATS (JetStream) | | In from | designed: middleware / edge | | Out to | designed: `USAGE_WRITER` → books | | Running | **stream exists; no writer; middleware uses local usage files** | Prepaid meter that **is** wired uses `verae.billing.usage.recorded` (core pub), not this stream. Finish: either publish here from `recordUsage` or drop the stream. --- ## Billing (`verae.billing.*`) — account-balance source of truth Request-reply unless noted. Queue `account-balance`. **Not** JetStream. | Address | For | In from | Out to | |---------|-----|---------|--------| | `statement.get` | prepaid, credits, usage, payments | portal, CS, sales, accounting, access-web/api | reply to requester | | `balance.get` | cents only | internal | reply | | `balance.adjust` | CS credit or portal reload | CS, access-web (reload kind only) | books + reply | | `usage.recorded` | meter pub | zappier-edge `meter()` `onUsage` when `NATS_URL` set | account-balance subscriber (debits prepaid) | | `payment.recorded` | reload/invoice pub | portal | books | | `credit.applied` | goodwill pub | CS/admin | books | | `customer.put` | upsert customer | edge ledger | books | Authz **must** pass `verae.access.authz.check` before these from a public plane. Leaf plane **cannot** call billing (policy). Middleware `recordUsage` still writes **local tenant files** and does **not** publish `usage.recorded`. Two meters until that is unified. --- ## Access planes | Address | For | In | Out | |---------|-----|----|-----| | `verae.access.authz.check` | allow/deny `{ plane, subject, principal }` | every plane + edge `billing-nats.ts` | `{ allow, reason }`; deny also pubs `authz.deny` | | `verae.access.authz.deny` | audit | authz on deny | **nowhere yet** (no SIEM) | | `verae.access.web.in` / `verae.access.web.billing.*` | browser portal | access-web HTTP | `statement.get` / `balance.adjust` kind=reload after authz | | `verae.access.api.*` | customer `x-api-key` | access-api | statement; HTTPS to middleware for jobs | | `verae.access.zapier.*` | Zapier HTTPS only | access-zapier | jobs.watch **via HTTP to middleware**, not Zapier NATS; usage.recorded allowed by policy | | `verae.access.leaf.in` | S2S `{ token, target, payload }` | remote leaf | archive + jobs after authz; **never billing** | | `verae.access.staff.*` | CS/sales/accounting | access-staff | billing after authz | Ingress mapping: `accessAddress(plane, 'verae.billing.statement.get')` → `verae.access..billing.statement.get`. --- ## Identity | Address | For | In | Out | |---------|-----|----|-----| | `verae.identity.bind` | bind email/customerId → `veraeUserId` | portal / middleware | reply | | `verae.identity.lookup` | lookup by customerId or veraeUserId | same | reply | Queue `identity`. Fleet keepFloor. --- ## Archive / tree | Address | For | In | Out | |---------|-----|----|-----| | `verae.archive.put` | WORM/tree write (`kind`: publicMeta / privateMeta / file / tree) | splitter (in-process from middleware today) | every worm/tree (JS) | | `verae.archive.query` | broadcast lookup | aggregator | **all** worm/tree, **no** queue group | | `verae.archive.reply.` | bloom **hit only** | worm/tree | aggregator | Silence = miss. Chain seal does not wait on archive. Bytes never go to the chain. --- ## Template / unused | Address | For | In | Out | Running | |---------|-----|----|-----|---------| | `verae.splitter.in` | future split-out splitter process | middleware | splitter | **no** | | `verae.example.process.in` | template worker | any | `example-process` Q | **no** | | `verae.example.process.out` | template fan-out | template | peers | **no** | | `verae.example.process.reply.` | template reply | template | requester | **no** | | `verae.internal.jobs.events` | exclusive mailbox republish | jobs-events | wait/webhook | **no** unless env | --- ## How to connect **Verae API** to NATS `api.veraetime.net` is **HTTPS**. It does not subscribe. The glue is middleware: 1. Set `VERAE_API_BASE_URL=https://api.veraetime.net` and `MOCK_VERAE=false`. 2. Middleware login (username/password) → JWT stored as `tokenRef`. 3. `POST /api/timestamp` → 202 `{ jobId }`. 4. Publish `verae.zapier.jobs.watch`. 5. Poller `GET /api/status/{jobId}` until `completed` / `failed`. 6. Publish `verae.zapier.jobs.events`. 7. Wait HTTP and/or REST Hook. Do **not** point Verae at `NATS_URL`. Do **not** put NATS in Zapier env. A new worker that must see seals uses `jobs.events` or HTTP `GET /zapier/v1/status/{jobId}`. Template for a new addressed process: clone `verae-nats-process`, rename `verae.example.process.*`, add the subject to `verae-access-authz` policy for the plane that may call it.