Documentation
Platform overview β low-cost build
How seal.club splits seal_api (PDF sealing, a separate service) from a thin Vercel site (marketing, business portal, keys, cert orders).
This is the build outline for a low-cost, automation-first platform. PDF freight and sealing run in seal_api, a separate service outside this repo; everything that looks like βnormal SaaSβ β marketing, the business portal, the admin back office β stays on Vercel in this repo and stays thin. Both share one Neon Postgres database: this repo owns the app.* schema, seal_api reads it directly.
Golden rules (cost)
| Rule | Why |
|---|---|
| Never proxy PDF bodies through Vercel | Egress and function limits dominate at volume β see /plan. |
| seal_api owns bytes | Upload, stage, sign, deliver via R2 + one HTTP handler (no queue fleet) β seal_api service design. |
| Vercel owns commerce metadata | Businesses, cert orders, certs, seal profiles, API key records, UI β JSON and forms, not PDFs β in the app Postgres schema (see below). |
| One timestamp per seal | SSL.com cert seat is the paid COGS; the per-job TSA stamp rides the free rfc3161.ai.moda/adobe router at $0 (/plan SSL.com table). |
| Lifecycle everything | R2 prefixes expire; control-plane DB keeps receipts and ids, not blobs. |
| One database, no sync jobs | seal_api (a separate Python service) reads the same Neon Postgres app.* tables directly β no D1 mirror, no async key-sync worker. |
Two-plane architecture
flowchart TB
subgraph vercel [Vercel β seal.club]
M[(marketing) prerender]
PORTAL[Business portal - dashboard, certificates, seals, keys]
ADMIN[Admin back office - admin/businesses]
DB[(Neon Postgres - app schema)]
end
subgraph sealapi [seal_api - separate service]
API[HTTP API β inline sign]
R2[(R2 staging)]
end
subgraph ext [External]
KMS[GCP KMS / HSM]
TSA[rfc3161.ai.moda/adobe]
SSL[SSL.com CA β cert issue]
INT[Integrators]
end
User[Browser] --> M
User --> PORTAL
Staff[seal.club staff] --> ADMIN
PORTAL --> DB
ADMIN --> DB
INT -->|Bearer sk_ direct| API
API -->|reads business/cert/seal/key rows directly| DB
API --> R2
API --> KMS
API --> TSA
API --> INT | Plane | Host | Responsibility |
|---|---|---|
| Data plane | seal_api (api.seal.club) | POST /v1/jobs/seal, presigned uploads, inline KMS sign + RFC 3161 (~300ms), presigned download, webhooks. |
| Control plane | Vercel (seal.club) | Marketing, docs shell, business portal, cert order pipeline, seal profiles, API key issuance UI, admin back office. |
Integrators should call seal_api directly with sk_β¦ keys. The Vercel site is for humans β it does not forward or proxy seal jobs. seal_api reads the shared app.* tables in the same Neon database directly, so there is no key-sync or catalog-sync job to run between the two.
seal_api β design document
The authoritative service design is Seal API β R2-backed system design (seal_api, a separate service from this repo β this repo is marketing, docs, and the b2b portal only).
Summary of what lives only there:
- Public API:
/v1/jobs/seal, job status, presigned PUT/GET, webhooks (API overview). - R2 bucket prefixes:
ingest/,artifacts/,quarantine/with lifecycle TTL. - Synchronous sign path in the same HTTP handler β stream from R2,
GCP KMS asymmetricSign(SSL.com-issued chain, or the external-HSM key once a businessβs HSM upgrade is enabled), RFC 3161 viahttps://rfc3161.ai.moda/adobe, write artifact (~300ms p95 in signing tests; no queue fleet). - Job ledger (id, phase, business id, object keys, receipt digest β no PDF columns) lives in seal_apiβs own store.
- Auth: validate
Bearer sk_β¦by reading theapp.api_keytable directly from the shared Neon database (hash comparison) β no sync step.
Estimated infra envelope at serious volume: cost section in seal-api-r2 (~low hundreds USD/mo Cloudflare-shaped vs multiβthousand egress elsewhere).
Vercel site β thin control plane
What stays on this repoβs SvelteKit app (Vercel adapter):
Marketing & docs (cheap)
(marketing)/*β prerendered landing,/plan, blog, pricing (already the pattern).- Mintlify
/docsβ edge rewrite only; no sealing logic.
Business portal (loose API, heavy UI)
Live routes map to control-plane concerns, not sealing. Each signed-in team workspace (auth.organization) has exactly one app.business row:
| Route area | Purpose | Backing table |
|---|---|---|
/onboarding | Collect legal identity for organization validation | app.business |
/dashboard | Status summary | app.* aggregates |
/certificates | Request a cert order; complete the validation checklist (uploads); view order + HSM upgrade status | app.certificate_order, app.certificate, app.validation_requirement, app.validation_document |
/seals, /seals/[id] | Seal profile config (visual/policy JSON) | app.seal_profile |
/keys | Mint / revoke API keys | app.api_key |
/admin/businesses | Staff-only: drive the manual SSL.com order + HSM upgrade pipeline | app.certificate_order, app.order_event |
Server pattern: +page.server.ts load/actions β not a public REST surface. Integrators never call these routes; they call seal_api directly with sk_β¦ keys.
What Vercel server code may call
- Neon (
app.*schema): the single source of truth for who owns which business, cert, seal profile, and key. No other store to keep in sync. - SSL.com is not called programmatically from this repo β order placement, validation, issuance, and the external-HSM ticket are recorded by staff via
/admin/businessesforms, not API calls.
What Vercel must not do
- Store sealing PDFs in Postgres,
/tmp, or Vercel Blob as part of the hot path. (A private Vercel Blob store is used for one thing only: customer-uploaded organization-validation documents β government IDs, incorporation docs β collected at/certificatesbefore staff touch SSL.com. See$lib/server/blob.tsand$lib/server/validation-docs.ts. This is unrelated to sealing traffic.) - Run GCP KMS or TSA calls at scale (occasional dev stub is fine).
- Terminate integrator traffic for
POST /v1/jobs/sealat scale β thatβsseal_api.
How the planes connect
Businesses & API keys
- A signed-in team workspace completes
/onboardingβ oneapp.businessrow (legal identity for organization validation β no domain involved). - The business mints
sk_live_β¦/sk_test_β¦at/keysβ row inapp.api_key(hash + prefix + business id); live keys require an active certificate. - seal_api reads
app.api_keydirectly from the same Neon database to validateBearertokens β no sync job, no second copy of key material.
No PDF crosses this boundary β only metadata.
Orders & certificates β sealing capability
- A business requests a certificate from
/certificatesβapp.certificate_orderrow (status: requested), which seeds 4app.validation_requirementrows (legal_existence,identity_verification,physical_existence,order_authorizationβ domain validation is excluded, since document-signing certs carry no domain).physical_existenceandorder_authorizationare both sourced from Dun & Bradstreet specifically (anchored onbusiness.duns_number), not an arbitrary database the customer picks. - The customer completes the checklist in-portal before staff are involved: structured detail fields plus document uploads (government ID, incorporation docs, D&B screenshots) stored in a private Vercel Blob store (
app.validation_document.blob_pathname, streamed back through an authorized route β never a public URL). Each requirement is submitted independently and locks until staff either request changes (with a note, emailed to the business) or reopen it. - Staff review the whole checklist holistically in
/admin/businesses/[id]β there is no hard gate tying the checklist to the next step; staff use judgment and βRequest changesβ for anything that looks wrong, then place the SSL.com organization document-signing order manually (~$209/yr seat β/plan), recording the SSL.com order ref and validation-documents URL, and progress the order through validation toissued, at which point anapp.certificaterow is created.business.validation_statustracks the coarse stage (docs_requestedβsubmitted_to_caβvalidated/rejected). - External-HSM upgrade (optional, staff-driven β no eSigner cloud): the business requests the upgrade; staff open an SSL.com support ticket, perform the key attestation ceremony themselves, and record the KMS key version once enabled. The customer portal only ever shows a coarse βrequested / in progress / enabledβ stage β ticket refs and the attestation step are admin-only detail (see
app.order_event.internal). - Runtime (every job): seal_api calls GCP KMS to sign the PDF digest and
rfc3161.ai.moda/adobefor the timestamp β SSL.com is not invoked per seal. - Customer cancel β SSL.com prorated credit; staff mark the order
cancelled; seal_api rejects new jobs once the certificate is no longeractive. A cancelled/rejected orderβs validation-requirement data (details + documents) is copied forward to the next order the business opens, reset toawaiting_inputfor reconfirmation, so customers arenβt re-uploading government IDs on every retry.
Seal profiles
/seals/[profileId] config JSON (appearance, policy labels) is control-plane config stored in app.seal_profile.config_json, optionally bound to an app.certificate row. seal_api reads the profile directly from Neon on job create and applies appearance + metadata fields (/Reason, /Location, document id strings) at sign time.
Build phases (low cost, in order)
Phase 0 β Contract & docs (now)
Freeze public shapes: Seal job, seal-api-r2, platform
overview. Vercel hub keeps simulated receipts when SEAL_API_* unset.
Phase 1 β seal_api skeleton
Health, auth stub reading app.api_key from Neon, R2 bucket + lifecycle, POST /v1/jobs/seal β
presigned PUT path + stub inline complete (no real sign yet). Deploy api.sandbox.seal.club.
Phase 2 β Real sign + TSA
Inline handler: stream from R2, GCP KMS sign + rfc3161.ai.moda/adobe timestamp, write artifacts/β¦, receipt in seal_apiβs own job ledger, return completed job in the same request
(~300ms target).
Phase 3 β Business portal (done)
Onboarding, cert order + external-HSM upgrade tracking, seal profiles, and API keys live in the app.* Neon schema described above β seal_api reads them directly, no sync step required.
Phase 4 β Commerce automation (optional)
SSL.com order-status polling to reduce manual staff status checks, cancel β prorated credit handling, quotas enforced in seal_api. Order placement and the external-HSM ticket stay staff-driven regardless β there is no self-serve SSL.com integration.
Environment map
| Surface | Production | Sandbox |
|---|---|---|
| Marketing + portal | https://seal.club (Vercel) | Preview deployments |
| seal_api | https://api.seal.club/v1 | https://api.sandbox.seal.club/v1 |
| Docs | Mintlify /docs | Local / Mint preview |
Related docs
- seal_api service design β R2, synchronous inline seal, sequences, infra cost math
- Seal job API
- Integration checklist
- Business plan β economics