master-zapier-plan-draft/research/zapier/verae-zapier-api/docs/developer/modules/function-reference.md
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

10 KiB

Function Reference — All Modules

Canonical I/O contracts for the Verae Zapier middleware and Zapier app.
Source of truth for reviewers; keep in sync with JSDoc in code.

Namespaces for debug: auth, billing, http, nats, jobs, webhooks, trace, app.


config.js

loadEnvFile()

For Load .env into process.env if keys are unset
Input none (reads file next to package root)
Output void

config (exported object)

Field Type Description
port number HTTP port
host string Bind address
veraeApiBaseUrl string Upstream Verae base URL (no trailing slash)
mockVerae boolean Use mock client
natsEnabled boolean Use JetStream workers
natsUrl string NATS connection URL
tokenSecret string HMAC secret for zmt_ tokens
jobPollIntervalMs number Poll delay
jobPollMaxAttempts number Max poll attempts
storePath string Absolute path to store file
upgradeUrl string Billing upgrade link for 402 bodies
adminSecret string Admin route secret

PLAN_LIMITS

Map of plan name → { timestamps, verifications, batch, batchMaxItems, requestsPerMinute }.
null means unlimited.


errors.js

class AppError extends Error

For Structured operational errors returned as JSON
Constructor (message: string, { status?, code?, details? })
Properties status: number, code: string, details: unknown

asyncHandler(fn)

For Wrap async Express handlers so rejections hit error middleware
Input (req, res, next) => Promise<any>
Output Express middleware function

sendError(res, err)

For Write JSON error response; log 5xx with debug
Input Express res, Error|AppError
Output void

app.js / index.js

createApp()

For Build Express application (no listen)
Input none
Output express.Application
Mounts GET /health, /zapier/*, error handler, traceMiddleware

main() (index.js)

For Boot store, optional NATS, workers, listen
Input none
Output Promise<void>

store/db.js

loadStore()

For Load JSON store into memory
Output store object

getStore()

For Access in-memory store
Output { tenants, apiKeys, usage, webhooks, jobWatchers }

persist()

For Flush store to disk
Output void

store/tenants.js

getTenant(tenantId)

| Input | tenantId: string | | Output | Tenant\|null |

getTenantByApiKey(apiKey)

| Input | apiKey: string | | Output | Tenant\|null |

listTenants()

| Output | Tenant[] |

createTenant({ id, name, plan, veraeUsername, veraePassword, contract?, apiKey?, metadata? })

| Output | { tenant: Tenant, apiKey: string } |

resolveLimits(tenant)

| Output | Effective limits including enterprise contract overrides |


store/usage.js

getUsage(tenantId)

| Output | counters for current period |

getUsageSummary(tenantId)

| Output | public-safe usage + limits snapshot |

incrementUsage(tenantId, metric, amount?)

| For | Atomically increment a counter and persist |


store/webhooks.js

createWebhook({ tenantId, targetUrl, event })

| Output | { id, tenantId, targetUrl, event, createdAt } |

deleteWebhook({ tenantId, hookId?, targetUrl? })

| Output | boolean removed |

getActiveWebhooks(tenantId, event)

| Output | Webhook[] |

listWebhooksForTenant(tenantId)

| Output | Webhook[] |


lib/tokens.js

issueSessionToken({ tenantId, veraeToken, expiresAt })

| Output | string (zmt_…) |

parseSessionToken(token)

| Output | { tenantId, veraeToken, expiresAt, nonce }\|null |

generateApiKey()

| Output | string (zmw_…) |

isApiKey(value)

| Output | boolean |

extractBearerToken(header)

| Input | Authorization header string | | Output | token string or null |


clients/veraeClient.js

All methods that hit the network log under DEBUG_VERAE=http.

veraeClient.login(credentials)

| Input | { username, password } | | Output | { token, expiresAt, user } |

veraeClient.validate(token)

| Input | Verae JWT | | Output | validation object |

veraeClient.createTimestamp(token, body)

| Input | JWT, { data, hashAlg? } | | Output | { jobId } |

veraeClient.createBatchTimestamp(token, body)

| Input | JWT, { items: [{ data, hashAlg? }] } | | Output | { jobIds: string[] } |

veraeClient.getStatus(token, jobId)

| Output | StatusResponse |

veraeClient.getBatchStatus(token, body)

| Input | { jobIds: string[] } | | Output | { results } |

veraeClient.verify(token, body)

| Input | { certificate } | | Output | { valid, timestamp?, blockIndex? } |

veraeClient.verifyBatch(token, body)

| Input | { certificates: string[] } | | Output | { results } |

veraeClient.waitForJob(token, jobId, { maxAttempts, intervalMs })

| Output | terminal StatusResponse or throws GATEWAY_TIMEOUT |


services/authService.js

Debug namespace: auth.

loginWithCredentials({ username, password, tenant? })

| Output | { accessToken, expiresAt, tenant, user } |

loginWithApiKey(apiKey)

| Output | same as loginWithCredentials after tenant lookup |

resolveAuthContext(rawToken)

| Input | API key or session token | | Output | { tenantId, tenant, veraeToken, authMethod } |

validateSession(rawToken)

| Output | { valid, tenantId, authMethod, user } |


services/entitlementService.js

Debug namespace: billing.

checkEntitlement(tenantId, action, { amount? })

| Actions | timestamp, verify, batch_timestamp | | Throws | 402 QUOTA_EXCEEDED, 403 PLAN_UPGRADE_REQUIRED | | Output | { tenant, limits, usage } |

recordUsage(tenantId, action, { amount? })

| Output | void |


middleware/authenticate.js

authenticate(req, res, next)

| For | Populate req.auth via resolveAuthContext | | Reads | Authorization: Bearer or x-api-key |

middleware/rateLimit.js

rateLimit(req, res, next)

| For | Enforce plan requestsPerMinute | | Throws | 429 RATE_LIMITED |


services/timestampService.js

Debug: jobs, billing, http.

createTimestamp(ctx, body)

| Input | auth context, { data, hashAlg? } | | Output | { jobId } | | Side effects | usage++, enqueue watch (NATS or in-process) |

createTimestampAndWait(ctx, body)

| Output | StatusResponse (or pending on timeout when NATS wait enabled) |

createBatchTimestamp(ctx, body)

| Output | { jobIds } |

getJobStatus(ctx, jobId) / getBatchJobStatus / getJobVerification

| Output | status payloads from Verae |


services/verifyService.js

verifyTimestamp(ctx, body) / verifyBatch(ctx, body)

| Output | verify results; records usage |


services/webhookService.js

Debug: webhooks.

subscribe(ctx, { targetUrl, event })

| Output | webhook record |

unsubscribe(ctx, { hookId?, targetUrl? })

| Output | { removed: true } |

deliverWebhook(targetUrl, payload)

| Output | { ok: boolean, status: number } |


services/tenantService.js

selfServeSignup({ email, name, veraeUsername, veraePassword })

| Output | { tenant, apiKey, zapierSetup } |

provisionTenant({ id?, name, plan, veraeUsername, veraePassword, contract?, metadata?, audience? })

| Output | { tenant, apiKey } |

listProvisionedTenants()

| Output | public tenant summaries (no passwords) |


nats/subjects.js

Constants

SUBJECTS.JOBS_WATCH, JOBS_EVENTS, WEBHOOKS_DELIVER, USAGE
STREAMS.ZAPIER_JOBS, ZAPIER_EVENTS, ZAPIER_WEBHOOKS


nats/connection.js

connectNats(url?)

| Output | { nc, js, jsm } NATS connection handles |

ensureStreams(jsm)

| For | Idempotent stream create | | Output | Promise<void> |

closeNats()

| Output | Promise<void> |


nats/publishers.js

enqueueWatch(jobWatchPayload)

| Input | see architecture/nats-subjects.md | | Output | Promise<{ seq }> |

publishJobEvent(eventPayload)

| Output | Promise<void> |

enqueueWebhook(deliverPayload)

| Output | Promise<void> |


workers/jobPollerWorker.js

startJobPollerWorker()

| For | Pull-consume watch queue; poll Verae; emit events | | Output | stop handle { stop() } |

workers/webhookWorker.js

startWebhookWorker()

| For | Deliver POSTs to Zapier with retry via JetStream | | Output | { stop() } |

workers/inProcessJobPoller.js

startInProcessJobPoller() / stopInProcessJobPoller()

| For | Phase 6 fallback when NATS_ENABLED=false |


Routes (all under /zapier)

Method Path Handler purpose
POST /v1/auth/login Credentials or api_key → session
GET /v1/auth/me Validate connection for Zapier
POST /v1/timestamp Async create
POST /v1/timestamp/wait Create and wait
POST /v1/timestamp/batch Batch create
POST /v1/verify Verify certificate
GET /v1/status/:jobId Job status
POST /v1/webhooks/subscribe REST Hook subscribe
DELETE /v1/webhooks/unsubscribe REST Hook unsubscribe
POST /v1/signup Self-serve tenant
POST /v1/admin/tenants Admin provision

Zapier app modules

authentication.test / fields

| Input from user | api_key | | Test URL | GET {MIDDLEWARE_BASE_URL}/zapier/v1/auth/me |

beforeRequest: addApiKey

| Sets | Authorization: Bearer ${bundle.authData.api_key} |

Creates

Key Middleware path
timestamp_and_wait POST /zapier/v1/timestamp/wait
create_timestamp POST /zapier/v1/timestamp
verify_timestamp POST /zapier/v1/verify
batch_timestamp POST /zapier/v1/timestamp/batch

Search job_status

| Path | GET /zapier/v1/status/{jobId} |

Trigger timestamp_completed

| Subscribe | POST /zapier/v1/webhooks/subscribe | | Unsubscribe | DELETE /zapier/v1/webhooks/unsubscribe | | Perform | return [bundle.cleanedRequest] |