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).

Edit on GitHub

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)

RuleWhy
Never proxy PDF bodies through VercelEgress and function limits dominate at volume β€” see /plan.
seal_api owns bytesUpload, stage, sign, deliver via R2 + one HTTP handler (no queue fleet) β€” seal_api service design.
Vercel owns commerce metadataBusinesses, cert orders, certs, seal profiles, API key records, UI β€” JSON and forms, not PDFs β€” in the app Postgres schema (see below).
One timestamp per sealSSL.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 everythingR2 prefixes expire; control-plane DB keeps receipts and ids, not blobs.
One database, no sync jobsseal_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
PlaneHostResponsibility
Data planeseal_api (api.seal.club)POST /v1/jobs/seal, presigned uploads, inline KMS sign + RFC 3161 (~300ms), presigned download, webhooks.
Control planeVercel (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 via https://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 the app.api_key table 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 areaPurposeBacking table
/onboardingCollect legal identity for organization validationapp.business
/dashboardStatus summaryapp.* aggregates
/certificatesRequest a cert order; complete the validation checklist (uploads); view order + HSM upgrade statusapp.certificate_order, app.certificate, app.validation_requirement, app.validation_document
/seals, /seals/[id]Seal profile config (visual/policy JSON)app.seal_profile
/keysMint / revoke API keysapp.api_key
/admin/businessesStaff-only: drive the manual SSL.com order + HSM upgrade pipelineapp.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/businesses forms, 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 /certificates before staff touch SSL.com. See $lib/server/blob.ts and $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/seal at scale β€” that’s seal_api.

How the planes connect

Businesses & API keys

  1. A signed-in team workspace completes /onboarding β†’ one app.business row (legal identity for organization validation β€” no domain involved).
  2. The business mints sk_live_… / sk_test_… at /keys β†’ row in app.api_key (hash + prefix + business id); live keys require an active certificate.
  3. seal_api reads app.api_key directly from the same Neon database to validate Bearer tokens β€” no sync job, no second copy of key material.

No PDF crosses this boundary β€” only metadata.

Orders & certificates β†’ sealing capability

  1. A business requests a certificate from /certificates β†’ app.certificate_order row (status: requested), which seeds 4 app.validation_requirement rows (legal_existence, identity_verification, physical_existence, order_authorization β€” domain validation is excluded, since document-signing certs carry no domain). physical_existence and order_authorization are both sourced from Dun & Bradstreet specifically (anchored on business.duns_number), not an arbitrary database the customer picks.
  2. 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.
  3. 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 to issued, at which point an app.certificate row is created. business.validation_status tracks the coarse stage (docs_requested β†’ submitted_to_ca β†’ validated / rejected).
  4. 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).
  5. Runtime (every job): seal_api calls GCP KMS to sign the PDF digest and rfc3161.ai.moda/adobe for the timestamp β€” SSL.com is not invoked per seal.
  6. Customer cancel β†’ SSL.com prorated credit; staff mark the order cancelled; seal_api rejects new jobs once the certificate is no longer active. A cancelled/rejected order’s validation-requirement data (details + documents) is copied forward to the next order the business opens, reset to awaiting_input for 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

SurfaceProductionSandbox
Marketing + portalhttps://seal.club (Vercel)Preview deployments
seal_apihttps://api.seal.club/v1https://api.sandbox.seal.club/v1
DocsMintlify /docsLocal / Mint preview