# Endpoint I/O Each product NATS address: **what it is for**, **who sends**, **who receives**, **how to attach**. Connect with a **long-lived** client: ```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. --- ## `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 | 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. --- ## `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 | Slow consumer: this is the flood/backlog path. Keep replica floors on the worker. --- ## `verae.zapier.usage` | | | |--|--| | For | Optional meter of Zap actions on NATS | | In from | designed: middleware / edge | | Out to | designed: `USAGE_WRITER` → books | | Running | **stream exists; no writer; middleware uses local usage files** | Finish: publish here from `recordUsage`, consume into `verae.billing.usage.recorded`. --- ## Billing (`verae.billing.*`) — account-balance SoT Request-reply unless noted. Queue on account-balance. **Not** JetStream (except you could mirror later). | Address | For | In from | Out to | |---------|-----|---------|--------| | `statement.get` | prepaid, credits, usage, payments | portal, CS, sales, admin, 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 (designed) | account-balance subscriber | | `payment.recorded` | reload/invoice pub | portal | books | | `credit.applied` | goodwill pub | CS | books | | `customer.put` | upsert customer | edge/IAM | books | Authz **must** pass `verae.access.authz.check` before these from a public plane. Leaf plane **cannot** call billing (policy). --- ## Access planes | Address | For | In | Out | |---------|-----|----|-----| | `verae.access.authz.check` | allow/deny `{ plane, subject, principal }` | every plane | `{ allow, reason }`; deny also pubs `authz.deny` | | `verae.access.web.*` | browser portal | access-web HTTP | billing statement/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 | | `verae.access.leaf.*` | S2S | access-leaf | archive + jobs; **never billing** | | `verae.access.staff.*` | CS/sales/accounting | access-staff | billing after authz | --- ## 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 (hash, meta, file meta, merkle leaf) | 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. --- ## 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.*`.