docs: Application Client developers page and OpenAPI on site

This commit is contained in:
George Lambert 2026-09-29 11:01:10 -04:00
commit 6be2afd683
4 changed files with 1002 additions and 0 deletions

268
site/developers.html Normal file
View file

@ -0,0 +1,268 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Application Client · Developers</title>
<meta name="description" content="Non-AI Application Client for Verae timestamps: OpenAPI, Python, TypeScript, curl VeraeTime global, curl MCP local. Digests only.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="icon" href="assets/VeraeFullLogo.png">
<style>
:root {
--ink: oklch(0.21 0.020 310);
--ink-soft: oklch(0.42 0.018 310);
--ink-mute: oklch(0.60 0.014 310);
--paper: oklch(0.992 0.004 90);
--soft: oklch(0.972 0.008 85);
--hair: oklch(0.90 0.010 80);
--purple: oklch(0.32 0.110 310);
--purple-mid: oklch(0.42 0.130 312);
--purple-wash: oklch(0.96 0.020 312);
--gold: oklch(0.80 0.090 85);
--dark: oklch(0.19 0.022 312);
--ok: oklch(0.55 0.12 145);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--ink);
background: var(--paper);
line-height: 1.55;
}
a { color: var(--purple); }
header {
display: flex; align-items: center; justify-content: space-between;
max-width: 1100px; margin: 0 auto; padding: 28px 28px 0; flex-wrap: wrap; gap: 12px;
}
.brand img { height: 46px; width: auto; display: block; }
nav { display: flex; gap: 22px; font-size: 14px; font-weight: 600; flex-wrap: wrap; }
nav a { color: var(--ink-soft); text-decoration: none; }
nav a:hover, nav a.active { color: var(--purple); }
.hero { max-width: 1100px; margin: 0 auto; padding: 48px 28px 12px; }
.eyebrow {
font-size: 13px; font-weight: 700; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--purple-mid);
}
h1 {
font-family: Fraunces, Georgia, serif; font-weight: 560;
font-size: clamp(34px, 5vw, 56px); line-height: 1.08;
letter-spacing: -0.03em; margin: 12px 0 18px; max-width: 22ch;
}
.lede { max-width: 46rem; font-size: 18px; color: var(--ink-soft); margin: 0; }
.gold { height: 3px; width: 72px; background: var(--gold); border: 0; margin: 28px 0; }
.wrap { max-width: 1100px; margin: 0 auto; padding: 12px 28px 80px; }
h2.section {
font-family: Fraunces, Georgia, serif; font-size: 30px; font-weight: 560; margin: 48px 0 8px;
}
.section-note { color: var(--ink-soft); max-width: 48rem; margin-top: 0; }
.card {
background: white; border: 1px solid var(--hair); border-radius: 16px; padding: 22px; margin-top: 18px;
}
.card.featured {
border-color: color-mix(in oklch, var(--purple) 35%, var(--hair));
background: linear-gradient(180deg, var(--purple-wash), white 48%);
}
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 14px; }
.btn {
display: inline-flex; align-items: center; gap: 8px;
background: var(--purple); color: white; text-decoration: none; border: 0;
border-radius: 999px; padding: 12px 18px; font: inherit; font-weight: 600; font-size: 14px; cursor: pointer;
}
.btn.secondary { background: white; color: var(--purple); border: 1px solid var(--hair); }
.meta { font-size: 13px; color: var(--ink-mute); margin-top: 12px; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
.path { background: var(--purple-wash); padding: 2px 6px; border-radius: 6px; }
pre {
background: var(--dark); color: oklch(0.95 0.01 90); border-radius: 14px;
padding: 16px 18px; overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.5; margin: 12px 0 0;
}
table.split {
width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 14px;
}
table.split th, table.split td {
text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--hair); vertical-align: top;
}
table.split th {
color: var(--ink-mute); font-weight: 600; font-size: 12px;
text-transform: uppercase; letter-spacing: 0.06em;
}
footer {
border-top: 1px solid var(--hair); padding: 28px; color: var(--ink-mute); font-size: 14px;
}
footer .inner { max-width: 1100px; margin: 0 auto; display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
@media (max-width: 800px) {
nav { gap: 14px; }
}
</style>
</head>
<body>
<header>
<a class="brand" href="https://www.verae.com/"><img src="assets/VeraeFullLogo.png" alt="Verae"></a>
<nav>
<a href="index.html">Install</a>
<a href="ways.html">Ways to use</a>
<a href="developers.html" class="active">Developers</a>
<a href="sample.html">Sample</a>
<a href="hasher.html">Hasher</a>
</nav>
</header>
<main>
<section class="hero">
<p class="eyebrow">Application Client · OpenAPI</p>
<h1>Build without an AI host</h1>
<p class="lede">Stamp, verify, and look up digests from Python, TypeScript, or raw HTTP. Paths in the OpenAPI file are <strong>logical SDK operations</strong> — not deployed REST routes on this host. File bytes never leave the client; only SHA-256 digests are sent.</p>
<hr class="gold">
</section>
<div class="wrap">
<section id="overview">
<h2 class="section">Overview</h2>
<p class="section-note">The Application Client routes by scope: <code>global</code> hits VeraeTime directly; <code>local</code> / <code>shared</code> use MCP Streamable HTTP at <code>/mcp</code>. SDKs refuse content-byte argument names (<code>content</code>, <code>bytes</code>, <code>file</code>, <code>data</code>, <code>path</code>, <code>body</code>, <code>payload</code>, <code>blob</code>, <code>archive</code>).</p>
<div class="card featured">
<p style="margin:0;color:var(--ink-soft);font-size:15px">Defaults: MCP <code>https://mcp.verae.ai/mcp</code> · VeraeTime <code>https://api.veraetime.net</code>. Global stamp sends <code>restamp: false</code> (reuse an existing certificate when present).</p>
<div class="btn-row">
<a class="btn" href="openapi/application-client.yaml">OpenAPI YAML</a>
<a class="btn secondary" href="ways.html">Ways to use hub</a>
<a class="btn secondary" href="sample.html">Browser MCP sample</a>
</div>
</div>
</section>
<section id="transport">
<h2 class="section">Transport split</h2>
<p class="section-note">One Application API surface; two backends. Auth ops always use VeraeTime with a caller-held JWT.</p>
<table class="split">
<thead>
<tr>
<th>Operation</th>
<th>local / shared</th>
<th>global</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>stamp</code></td>
<td>MCP tool <code>stamp</code></td>
<td>VeraeTime <code>POST /api/timestamp</code></td>
</tr>
<tr>
<td><code>verify</code></td>
<td>MCP tool <code>verify</code></td>
<td>VeraeTime <code>POST /api/verify</code></td>
</tr>
<tr>
<td><code>hash_report</code></td>
<td>MCP tool <code>hash_report</code></td>
<td>VeraeTime <code>GET /api/hash/{sha256}</code></td>
</tr>
<tr>
<td>signup / login / session / create_user</td>
<td colspan="2">VeraeTime <code>/auth/*</code> (direct; apps do not rely on MCP account tools)</td>
</tr>
</tbody>
</table>
</section>
<section id="openapi">
<h2 class="section">OpenAPI</h2>
<p class="section-note">Normative digests-only contract for the Application Client SDKs (OpenAPI 3.1). Download or fetch from this site:</p>
<pre>https://mcp.verae.ai/openapi/application-client.yaml</pre>
<div class="btn-row">
<a class="btn" href="openapi/application-client.yaml">application-client.yaml</a>
</div>
<p class="meta">Logical paths such as <code>/stamp</code> document SDK methods; they are not HTTP routes on <code>mcp.verae.ai</code> in this phase.</p>
</section>
<section id="python">
<h2 class="section">Python</h2>
<p class="section-note">Package <code>verae-mcp-timestamping-client</code> — <code>ApplicationClient</code> routes by scope.</p>
<pre>import asyncio
from verae_mcp_timestamping_client import ApplicationClient
async def main():
client = ApplicationClient()
print(await client.stamp("&lt;64-hex&gt;", scope="global"))
print(await client.stamp("&lt;64-hex&gt;", scope="local"))
asyncio.run(main())</pre>
<p class="meta">Install: <code>uv add verae-mcp-timestamping-client</code> or <code>pip install verae-mcp-timestamping-client</code>.</p>
</section>
<section id="typescript">
<h2 class="section">TypeScript</h2>
<p class="section-note">Package <code>@verae/timestamping-client</code>. Method names are camelCase (<code>hashReport</code>); OpenAPI stays snake_case.</p>
<pre>import { ApplicationClient } from "@verae/timestamping-client";
const client = new ApplicationClient({ token: process.env.VERAE_GLOBAL_TOKEN });
// Global → VeraeTime POST /api/timestamp (restamp: false)
const global = await client.stamp("a".repeat(64), { scope: "global" });
// Local → MCP tools/call stamp
const local = await client.stamp("a".repeat(64), { scope: "local" });</pre>
<p class="meta">Install: <code>npm install @verae/timestamping-client</code>.</p>
</section>
<section id="curl-global">
<h2 class="section">curl · global (VeraeTime)</h2>
<p class="section-note">Direct HTTPS to <code>api.veraetime.net</code>. Bearer token from env or <span class="path">~/.verae/auth.properties</span>.</p>
<pre># TOKEN from VERAE_GLOBAL_TOKEN or access_token in ~/.verae/auth.properties
DIGEST=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
curl -sS -X POST https://api.veraetime.net/api/timestamp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d "{\"data\":\"$DIGEST\",\"digest\":\"$DIGEST\",\"hashAlg\":\"SHA256\",\"restamp\":false}"</pre>
<p class="meta">On <code>200</code> with <code>certificate</code> / <code>existing</code>, reuse that CID. Otherwise poll <code>GET /api/status/{jobId}</code>.</p>
</section>
<section id="curl-mcp">
<h2 class="section">curl · MCP local</h2>
<p class="section-note">Streamable HTTP at <code>/mcp</code>. Capture <code>Mcp-Session-Id</code> from <code>initialize</code>, then <code>notifications/initialized</code>, then <code>tools/call</code>.</p>
<pre># 1) initialize — note Mcp-Session-Id in response headers
curl -sS -D - -X POST https://mcp.verae.ai/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"dev-curl","version":"1.0.0"}}}'
# 2) notifications/initialized (same session)
curl -sS -X POST https://mcp.verae.ai/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Session-Id: SESSION' \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3) tools/call stamp (local scope)
curl -sS -X POST https://mcp.verae.ai/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Session-Id: SESSION' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"stamp","arguments":{"sha256":"DIGEST_HEX","scope":"local"}}}'</pre>
</section>
<section id="auth">
<h2 class="section">Auth</h2>
<p class="section-note">Machine login writes a JWT under <span class="path">~/.verae/auth.properties</span> (mode <code>600</code>). Do not paste tokens into chat or commit the file.</p>
<div class="card">
<p style="margin:0 0 8px;color:var(--ink-soft);font-size:15px"><strong>Resolve order</strong> (Application Client / VeraeTime transport): constructor token → <code>VERAE_GLOBAL_TOKEN</code> → <code>access_token</code> / <code>auth_token</code> / <code>id_token</code> in auth.properties (<code>VERAE_AUTH_PROPERTIES</code> overrides the path).</p>
<pre>uvx verae-mcp-timestamping-server setup
# writes ~/.verae/auth.properties (mode 600); does not print the token
uvx verae-mcp-timestamping-server session</pre>
<p class="meta">Keys typically include <code>access_token</code>, <code>expires_at</code>, and username. Directory <code>~/.verae</code> is mode <code>700</code>.</p>
</div>
</section>
</div>
</main>
<footer>
<div class="inner">
<span>Verae timestamping · <a href="https://www.verae.com/">verae.com</a></span>
<span><a href="index.html">Install</a> · <a href="ways.html">Ways to use</a> · <a href="developers.html">Developers</a> · <a href="openapi/application-client.yaml">OpenAPI</a> · <a href="sample.html">Sample</a></span>
</div>
</footer>
</body>
</html>

View file

@ -168,6 +168,8 @@
<a href="#hasher">Hasher</a> <a href="#hasher">Hasher</a>
<a href="#connect">Connect</a> <a href="#connect">Connect</a>
<a href="#register">Register</a> <a href="#register">Register</a>
<a href="ways.html">Ways to use</a>
<a href="developers.html">Developers</a>
<a href="sample.html">Try a stamp</a> <a href="sample.html">Try a stamp</a>
</nav> </nav>
</header> </header>

View file

@ -0,0 +1,374 @@
openapi: 3.1.0
info:
title: Verae Application Client API
version: 0.1.0
description: |
Digests-only Application Client API for non-AI apps. Paths below are **logical**
documentation identifiers for the client SDKs (Python / TypeScript); they are **not**
deployed server routes on `mcp.verae.ai` in this phase.
**Defaults:** MCP Streamable HTTP `https://mcp.verae.ai/mcp`; VeraeTime
`https://api.veraetime.net`.
**Digests only:** stamp / verify / hash_report accept SHA-256 digests. SDKs refuse
arguments named `content`, `bytes`, `file`, `data`, `path`, `body`, `payload`,
`blob`, or `archive`.
**Global stamp:** VeraeTime body uses `restamp: false` by default (reuse existing
certificate). Explicit restamp is out of scope unless added later.
## Mapping appendix (MCP ↔ VeraeTime ↔ Application API)
| Application op | MCP tool | VeraeTime |
| --- | --- | --- |
| stamp local/shared | `stamp` | — |
| stamp global | — | `POST /api/timestamp` |
| verify (non-global) | `verify` | — |
| verify global | — | `POST /api/verify` |
| hash_report (non-global) | `hash_report` | — |
| hash_report global | — | `GET /api/hash/{sha256}` |
| signup | (`signup` exists on MCP; apps use direct) | `POST /auth/signup` |
| login | (`login` exists on MCP; apps use direct) | `POST /auth/login` |
| session | (`session` exists on MCP; apps use direct) | `GET /auth/validate` |
| create_user | (`create_user` exists on MCP; apps use direct) | `POST /auth/users` |
Routing: `local` / `shared` stamp and non-global verify / hash_report use MCP.
`global` / `want_scope=global` use VeraeTime directly. Auth ops always hit VeraeTime
with caller-held JWTs.
servers:
- url: https://mcp.verae.ai
description: >
Logical base for documentation only. Application Client SDK paths are not
deployed HTTP routes; local/shared ops use MCP at https://mcp.verae.ai/mcp.
- url: https://api.veraetime.net
description: >
VeraeTime direct HTTPS for global stamp/verify/hash_report and all auth ops.
Application Client paths still map to /api/* and /auth/* as in the appendix.
paths:
/stamp:
post:
operationId: stamp
summary: Stamp a SHA-256 digest
description: |
Request a timestamp for a digest. Scope `local` or `shared` goes to MCP tool
`stamp`; scope `global` goes to VeraeTime `POST /api/timestamp` with
`restamp: false` by default.
tags: [timestamping]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/StampRequest"
responses:
"200":
description: One or more normalized receipts
content:
application/json:
schema:
$ref: "#/components/schemas/StampResponse"
/verify:
post:
operationId: verify
summary: Verify a receipt
description: |
Verify by `receipt_id`. Use `want_scope=global` (or a global receipt) for
VeraeTime `POST /api/verify`; otherwise MCP tool `verify`.
tags: [timestamping]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/VerifyRequest"
responses:
"200":
description: Verification result
content:
application/json:
schema:
$ref: "#/components/schemas/VerifyResponse"
/hash_report:
post:
operationId: hash_report
summary: List receipts for a digest
description: |
History for a SHA-256 digest. `want_scope=global` uses VeraeTime
`GET /api/hash/{sha256}`; otherwise MCP tool `hash_report`. Empty history
is `{ "receipts": [] }`.
tags: [timestamping]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/HashReportRequest"
responses:
"200":
description: Receipts for the digest (possibly empty)
content:
application/json:
schema:
$ref: "#/components/schemas/HashReportResponse"
/signup:
post:
operationId: signup
summary: Create an account on VeraeTime
description: |
Always `POST https://api.veraetime.net/auth/signup`. Does not replace the
stored JWT; caller must `login` for a session.
tags: [auth]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SignupRequest"
responses:
"200":
description: Created user
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUserResponse"
/login:
post:
operationId: login
summary: Login and obtain caller-held JWT
description: |
Always `POST https://api.veraetime.net/auth/login`. SDK stores JWT in
caller-local secure storage (`~/.verae/auth.properties` on native). JWT
values are never logged.
tags: [auth]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LoginRequest"
responses:
"200":
description: Authenticated user session metadata
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUserResponse"
/session:
get:
operationId: session
summary: Validate current session
description: |
Always `GET https://api.veraetime.net/auth/validate` with bearer from
constructor, `VERAE_GLOBAL_TOKEN`, or auth.properties.
tags: [auth]
security:
- bearerAuth: []
responses:
"200":
description: Session validity
content:
application/json:
schema:
$ref: "#/components/schemas/SessionResponse"
/create_user:
post:
operationId: create_user
summary: Create a user (admin)
description: |
Always `POST https://api.veraetime.net/auth/users`. Requires admin bearer.
tags: [auth]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserRequest"
responses:
"200":
description: Created user
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUserResponse"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: Caller-held VeraeTime JWT (never logged).
schemas:
Receipt:
type: object
description: Shared normalized receipt object across MCP and VeraeTime.
required:
- receipt_id
- hash
- scope
- created_at
- merkle
properties:
receipt_id:
type: string
description: Local id or VeraeTime certificate CID
hash:
type: string
description: SHA-256 hex (64 characters)
pattern: "^[0-9a-fA-F]{64}$"
scope:
type: string
description: local, shared, or global
enum: [local, shared, global]
cube_id:
type: string
description: Optional; local metadata
org_id:
type: string
description: Optional; local idempotency key; ignored by VeraeTime
created_at:
type: string
description: Timestamp when available
merkle:
type: string
description: Block hash / index when available
restamp_of:
type: string
description: Prior certificate when restamped
StampRequest:
type: object
required: [sha256]
additionalProperties: false
properties:
sha256:
type: string
description: >
SHA-256 digest (64 hex). Optional `sha256:` prefix is stripped by SDKs.
minLength: 64
scope:
type: string
description: Default local. global → VeraeTime with restamp false.
enum: [local, shared, global]
default: local
cube_id:
type: string
org_id:
type: string
StampResponse:
type: object
required: [receipts]
properties:
receipts:
type: array
items:
$ref: "#/components/schemas/Receipt"
VerifyRequest:
type: object
required: [receipt_id]
additionalProperties: false
properties:
receipt_id:
type: string
want_scope:
type: string
description: Set to global to force VeraeTime verify.
enum: [local, shared, global]
VerifyResponse:
type: object
required: [valid, error, receipt]
properties:
valid:
type: boolean
error:
type: string
nullable: true
receipt:
nullable: true
allOf:
- $ref: "#/components/schemas/Receipt"
HashReportRequest:
type: object
required: [sha256]
additionalProperties: false
properties:
sha256:
type: string
description: SHA-256 digest (64 hex; optional sha256: prefix stripped)
minLength: 64
org_id:
type: string
want_scope:
type: string
description: Set to global to force VeraeTime hash lookup.
enum: [local, shared, global]
HashReportResponse:
type: object
required: [receipts]
properties:
receipts:
type: array
items:
$ref: "#/components/schemas/Receipt"
SignupRequest:
type: object
required: [username, password]
additionalProperties: false
properties:
username:
type: string
password:
type: string
format: password
LoginRequest:
type: object
required: [username, password]
additionalProperties: false
properties:
username:
type: string
password:
type: string
format: password
AuthUserResponse:
type: object
required: [username, role]
properties:
id:
type: string
username:
type: string
role:
type: string
expires_at:
type: string
SessionResponse:
type: object
required: [valid]
properties:
valid:
type: boolean
username:
type: string
role:
type: string
expires_at:
type: string
CreateUserRequest:
type: object
required: [username, password, role]
additionalProperties: false
properties:
username:
type: string
password:
type: string
format: password
role:
type: string
enum: [user, admin]
tags:
- name: timestamping
description: Digests-only stamp, verify, and hash history
- name: auth
description: VeraeTime-direct account and session operations

358
site/ways.html Normal file
View file

@ -0,0 +1,358 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ways to use Verae timestamps</title>
<meta name="description" content="Ways to use Verae timestamps: AI MCP, browser, macOS direct download (notarized), TestFlight, Windows GUI, CLI, JavaScript, curl, explorer.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="icon" href="assets/VeraeFullLogo.png">
<style>
:root {
--ink: oklch(0.21 0.020 310);
--ink-soft: oklch(0.42 0.018 310);
--ink-mute: oklch(0.60 0.014 310);
--paper: oklch(0.992 0.004 90);
--soft: oklch(0.972 0.008 85);
--hair: oklch(0.90 0.010 80);
--purple: oklch(0.32 0.110 310);
--purple-mid: oklch(0.42 0.130 312);
--purple-wash: oklch(0.96 0.020 312);
--gold: oklch(0.80 0.090 85);
--dark: oklch(0.19 0.022 312);
--ok: oklch(0.55 0.12 145);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--ink);
background: var(--paper);
line-height: 1.55;
}
a { color: var(--purple); }
header {
display: flex; align-items: center; justify-content: space-between;
max-width: 1100px; margin: 0 auto; padding: 28px 28px 0; flex-wrap: wrap; gap: 12px;
}
.brand img { height: 46px; width: auto; display: block; }
nav { display: flex; gap: 22px; font-size: 14px; font-weight: 600; flex-wrap: wrap; }
nav a { color: var(--ink-soft); text-decoration: none; }
nav a:hover, nav a.active { color: var(--purple); }
.hero { max-width: 1100px; margin: 0 auto; padding: 48px 28px 12px; }
.eyebrow {
font-size: 13px; font-weight: 700; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--purple-mid);
}
h1 {
font-family: Fraunces, Georgia, serif; font-weight: 560;
font-size: clamp(34px, 5vw, 56px); line-height: 1.08;
letter-spacing: -0.03em; margin: 12px 0 18px; max-width: 20ch;
}
.lede { max-width: 46rem; font-size: 18px; color: var(--ink-soft); margin: 0; }
.gold { height: 3px; width: 72px; background: var(--gold); border: 0; margin: 28px 0; }
.wrap { max-width: 1100px; margin: 0 auto; padding: 12px 28px 80px; }
h2.section {
font-family: Fraunces, Georgia, serif; font-size: 30px; font-weight: 560; margin: 48px 0 8px;
}
.section-note { color: var(--ink-soft); max-width: 48rem; margin-top: 0; }
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin-top: 22px; }
.card {
background: white; border: 1px solid var(--hair); border-radius: 16px; padding: 22px;
}
.card .step {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--purple-mid);
}
.card h3 { font-size: 18px; margin: 6px 0 8px; }
.card p { margin: 0; color: var(--ink-soft); font-size: 15px; }
.card.featured { border-color: color-mix(in oklch, var(--purple) 35%, var(--hair)); background: linear-gradient(180deg, var(--purple-wash), white 48%); }
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 14px; }
.btn {
display: inline-flex; align-items: center; gap: 8px;
background: var(--purple); color: white; text-decoration: none; border: 0;
border-radius: 999px; padding: 12px 18px; font: inherit; font-weight: 600; font-size: 14px; cursor: pointer;
}
.btn.secondary { background: white; color: var(--purple); border: 1px solid var(--hair); }
.meta { font-size: 13px; color: var(--ink-mute); margin-top: 12px; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
pre {
background: var(--dark); color: oklch(0.95 0.01 90); border-radius: 14px;
padding: 16px 18px; overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.5; margin: 12px 0 0;
}
label.field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-soft); margin-top: 12px; max-width: 28rem; }
input[type=email], input[type=text], textarea {
font: inherit; font-weight: 500; color: var(--ink);
border: 1px solid var(--hair); border-radius: 10px; padding: 10px 12px; background: var(--soft);
}
textarea { min-height: 72px; resize: vertical; }
footer {
border-top: 1px solid var(--hair); padding: 28px; color: var(--ink-mute); font-size: 14px;
}
footer .inner { max-width: 1100px; margin: 0 auto; display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
@media (max-width: 800px) {
.grid { grid-template-columns: 1fr; }
nav { gap: 14px; }
}
</style>
</head>
<body>
<header>
<a class="brand" href="https://www.verae.com/"><img src="assets/VeraeFullLogo.png" alt="Verae"></a>
<nav>
<a href="index.html">Install</a>
<a href="ways.html" class="active">Ways to use</a>
<a href="developers.html">Developers</a>
<a href="macos.html">macOS</a>
<a href="explorer.html">Explorer</a>
<a href="windows.html">Windows</a>
<a href="app.html">Browser app</a>
<a href="register.html">Register docs</a>
<a href="sample.html">Sample</a>
<a href="hasher.html">Hasher</a>
</nav>
</header>
<main>
<section class="hero">
<p class="eyebrow">Same MCP · many clients</p>
<h1>Ways to use Verae timestamps</h1>
<p class="lede">Hash locally; stamp digests only against the public Streamable HTTP endpoint. Start with <strong>AI MCP</strong>, then choose a browser, Mac/Windows UI, CLI, JS, or curl client. File bytes never upload.</p>
<hr class="gold">
</section>
<div class="wrap">
<section id="list">
<div class="grid">
<article class="card featured" id="ai-mcp">
<p class="step">1 · Start here</p>
<h3>AI MCP</h3>
<p>Point Claude.ai, ChatGPT, Grok, Claude Desktop, or Cursor at <code id="mcp-url">https://mcp.verae.ai/mcp</code>. The assistant calls <code>stamp</code> / <code>verify</code> / <code>hash_report</code>. First connect may open Verae browser login.</p>
<div class="btn-row">
<a class="btn" href="index.html#connectors">Connectors by host</a>
<a class="btn secondary" href="index.html#connect">mcp.json snippet</a>
</div>
</article>
<article class="card" id="browser">
<p class="step">2 · Browser</p>
<h3>Browser app</h3>
<p>Full Register / Verify client in the browser (WASM hash, auth defaults, CSV / JSON / PDF / text). Digests only.</p>
<div class="btn-row">
<a class="btn" href="app.html">Open browser app</a>
<a class="btn secondary" href="register.html">Multi-doc register</a>
<a class="btn secondary" href="sample.html">MCP sample</a>
</div>
</article>
<article class="card" id="explorer">
<p class="step">2 · Explorer</p>
<h3>Verify receipt or file hash</h3>
<p>Public lookup: paste a receipt CID, SHA-256, or drop a file (hashed in-browser). On-chain merkle opens in the <a href="https://explorer.verae.ai/">blockchain explorer</a>.</p>
<div class="btn-row">
<a class="btn" href="explorer.html">Timestamp explorer</a>
<a class="btn secondary" href="https://explorer.verae.ai/" target="_blank" rel="noopener">Chain explorer</a>
</div>
</article>
<article class="card featured" id="mac-ui">
<p class="step">3 · UI · Mac</p>
<h3>macOS direct download</h3>
<p>Native SwiftUI <strong>Verae Timestamping</strong> — Developer ID signed, <strong>Apple-notarized</strong> universal zip. No TestFlight. Download → unzip → Open.</p>
<div class="btn-row">
<a class="btn" href="macos.html">macOS download page</a>
<a class="btn secondary" href="/downloads/macos/VeraeTimestamping-macos-universal.zip">.app zip</a>
<a class="btn secondary" href="#testflight">TestFlight invite</a>
</div>
<p class="meta">Gatekeeper: Notarized Developer ID · macOS 14+ · quarantine clear only if a download flag still blocks Open.</p>
</article>
<article class="card" id="windows-ui">
<p class="step">3 · UI · Windows</p>
<h3>Windows GUI</h3>
<p>Frozen amd64 EXEs — PySide6 GUI, CLI, optional hasher. SmartScreen warns (not code-signed); verify SHA-256 first.</p>
<div class="btn-row">
<a class="btn" href="windows.html">Windows download page</a>
<a class="btn secondary" href="/downloads/windows/verae-timestamp-gui.exe">GUI EXE</a>
</div>
</article>
<article class="card" id="cli">
<p class="step">4 · CLI</p>
<h3>Command line</h3>
<p>Official Python client: hash locally, stamp via MCP, write receipts under <code>~/.verae/receipts/</code>. On Windows use <code>verae-timestamp.exe</code>.</p>
<pre id="cli-snip">uvx verae-mcp-timestamping-client \
--url https://mcp.verae.ai/mcp \
--scope global \
./document.pdf</pre>
<div class="btn-row">
<a class="btn secondary" href="index.html#apps">More CLI notes</a>
<a class="btn secondary" href="windows.html#cli">Windows CLI</a>
</div>
</article>
<article class="card" id="js">
<p class="step">5 · JavaScript</p>
<h3>Embed with fetch</h3>
<p>Any web app: WASM/WebCrypto hash in-page, then <code>POST</code> Streamable HTTP JSON-RPC to <code>/mcp</code> (CORS <code>*</code>). Session id from <code>Mcp-Session-Id</code>.</p>
<div class="btn-row">
<a class="btn" href="index.html#apps">JS snippet on Install</a>
<a class="btn secondary" href="app.html">See live client</a>
</div>
</article>
<article class="card" id="curl">
<p class="step">6 · curl</p>
<h3>Raw JSON-RPC</h3>
<p><code>initialize</code> → <code>notifications/initialized</code> → <code>tools/call</code> (<code>stamp</code>, <code>verify</code>, <code>hash_report</code>). Include <code>Accept: application/json, text/event-stream</code>.</p>
<div class="btn-row">
<a class="btn" href="index.html#apps">curl examples</a>
<a class="btn secondary" href="developers.html#curl-mcp">Developers curl</a>
</div>
</article>
<article class="card featured" id="application-client">
<p class="step">7 · Application Client</p>
<h3>Application Client / OpenAPI</h3>
<p>Non-AI apps: Python &amp; TypeScript SDKs, digests-only OpenAPI, curl to VeraeTime (global) or MCP (local/shared). Logical SDK paths — not REST routes on this host.</p>
<div class="btn-row">
<a class="btn" href="developers.html">Developers page</a>
<a class="btn secondary" href="openapi/application-client.yaml">OpenAPI YAML</a>
</div>
</article>
<article class="card" id="hasher">
<p class="step">Also</p>
<h3>Local hasher · uvx server</h3>
<p>Download <code>verae-sha256</code> for your OS, or hash in-browser with WASM. Optional local MCP: <code>uvx verae-mcp-timestamping-server</code> and <code>… setup</code> for machine login.</p>
<div class="btn-row">
<a class="btn secondary" href="index.html#hasher">Get hasher</a>
<a class="btn secondary" href="hasher.html">WASM hasher</a>
<a class="btn secondary" href="index.html#register">Machine login</a>
</div>
</article>
</div>
</section>
<section id="downloads">
<h2 class="section">Downloads hub</h2>
<p class="section-note">Stable artifact URLs on this host. macOS zip is Developer ID + notarized (not MAS).</p>
<div class="btn-row">
<a class="btn" href="explorer.html">Timestamp explorer</a>
<a class="btn" href="macos.html">macOS direct download</a>
<a class="btn" href="windows.html">Windows GUI + CLI</a>
<a class="btn secondary" href="/downloads/macos/SHA256SUMS">macOS SHA256SUMS</a>
<a class="btn secondary" href="/downloads/windows/SHA256SUMS">Windows SHA256SUMS</a>
<a class="btn secondary" href="/hasher/latest.json">Hasher latest.json</a>
<a class="btn secondary" href="#testflight">Mac TestFlight</a>
</div>
</section>
<section id="macos-direct">
<h2 class="section">macOS direct download (no TestFlight)</h2>
<p class="section-note">Universal <strong>Verae Timestamping.app</strong> (v0.2.1 / build 7), signed <em>Developer ID Application: FlightLookup Inc.</em> and Apple-notarized with staple. Gatekeeper accepts as <em>Notarized Developer ID</em>.</p>
<div class="card" style="margin-top:18px">
<ol style="padding-left:1.2rem;margin:0;color:var(--ink-soft);font-size:15px;max-width:48rem">
<li style="margin:0.45rem 0"><strong>Download</strong> <a href="/downloads/macos/VeraeTimestamping-macos-universal.zip"><code>VeraeTimestamping-macos-universal.zip</code></a> (~546&nbsp;KiB).</li>
<li style="margin:0.45rem 0"><strong>Unzip</strong> → drag the app to <code>/Applications</code> (optional).</li>
<li style="margin:0.45rem 0">If Gatekeeper still blocks a quarantined copy: right-click → <em>Open</em>, or run <code>xattr -dr com.apple.quarantine "/Applications/Verae Timestamping.app"</code>.</li>
<li style="margin:0.45rem 0"><strong>Open</strong> → first launch (MCP default <code>https://mcp.verae.ai/mcp</code>).</li>
</ol>
<div class="btn-row">
<a class="btn" href="macos.html">Full macOS instructions</a>
<a class="btn secondary" href="/downloads/macos/VeraeTimestamping-macos-universal.zip">Download zip</a>
<a class="btn secondary" href="/downloads/macos/SHA256SUMS">SHA256SUMS</a>
</div>
<p class="meta">CLI without the .app: <code>uv sync</code> then <code>uv run verae-timestamp …</code> in the client repo, or <code>uvx verae-mcp-timestamping-client</code> when published.</p>
</div>
</section>
<section id="testflight">
<h2 class="section">Request a macOS TestFlight invite</h2>
<p class="section-note">Optional App Store–style beta. Prefer the <a href="#macos-direct">direct notarized zip</a> if you do not want TestFlight. External testing still requires Apple Beta App Review — until that lands, invites are added manually for your Apple ID.</p>
<div class="card" style="margin-top:18px">
<h3 style="margin:0 0 8px;font-size:18px">Public join link</h3>
<p>When external Beta Review is approved, open:</p>
<p style="margin:10px 0"><a href="https://testflight.apple.com/join/dsHsyx2Z"><code>https://testflight.apple.com/join/dsHsyx2Z</code></a></p>
<p class="meta">If the link says the beta is full or unavailable, request an invite below.</p>
<div class="btn-row">
<a class="btn" href="https://testflight.apple.com/join/dsHsyx2Z">Open TestFlight join</a>
</div>
</div>
<div class="card" style="margin-top:18px">
<h3 style="margin:0 0 8px;font-size:18px">Request invite (Apple ID email)</h3>
<p>Submit the Apple ID email you use for TestFlight. The server adds you to the external testers group via App Store Connect and resends the invitation.</p>
<form id="tf-form">
<label class="field">Apple ID email
<input type="email" id="tf-email" required placeholder="you@example.com" autocomplete="email">
</label>
<label class="field">Optional note
<textarea id="tf-note" placeholder="Name, org, or why you need access"></textarea>
</label>
<div class="btn-row">
<button type="submit" class="btn" id="tf-submit">Request TestFlight invite</button>
<a class="btn secondary" href="mailto:marchon@gmail.com?subject=Verae%20Timestamping%20%E2%80%94%20macOS%20TestFlight%20invite%20request">Fallback: email marchon@gmail.com</a>
</div>
</form>
<p id="tf-status" class="meta" role="status" aria-live="polite" style="margin-top:14px"></p>
<p class="meta">Enrollment can succeed while the build is still <code>WAITING_FOR_BETA_REVIEW</code> — you may not see the app in TestFlight until Apple approves the beta.</p>
</div>
</section>
</div>
</main>
<footer>
<div class="inner">
<span>Verae timestamping · <a href="https://www.verae.com/">verae.com</a></span>
<span><a href="index.html">Install</a> · <a href="ways.html">Ways to use</a> · <a href="developers.html">Developers</a> · <a href="macos.html">macOS</a>
<a href="windows.html">Windows</a> · <a href="app.html">Browser app</a> · <a href="https://testflight.apple.com/join/dsHsyx2Z">TestFlight</a></span>
</div>
</footer>
<script>
(function () {
var host = location.hostname || "mcp.verae.ai";
var mcpUrl = "https://" + host + "/mcp";
var el = document.getElementById("mcp-url");
if (el) el.textContent = mcpUrl;
var snip = document.getElementById("cli-snip");
if (snip) snip.textContent = snip.textContent.replaceAll("https://mcp.verae.ai/mcp", mcpUrl);
var form = document.getElementById("tf-form");
var status = document.getElementById("tf-status");
var submit = document.getElementById("tf-submit");
var endpoint = (location.origin && location.origin.indexOf("http") === 0)
? (location.origin + "/api/testflight-invite")
: "https://mcp.verae.ai/api/testflight-invite";
function setStatus(msg, ok) {
if (!status) return;
status.textContent = msg;
status.style.color = ok === true ? "var(--ok)" : (ok === false ? "oklch(0.5 0.16 25)" : "var(--ink-mute)");
}
if (form) {
form.addEventListener("submit", async function (e) {
e.preventDefault();
var email = (document.getElementById("tf-email").value || "").trim();
var note = (document.getElementById("tf-note").value || "").trim();
if (!email) { setStatus("Enter an Apple ID email.", false); return; }
if (submit) submit.disabled = true;
setStatus("Enrolling via App Store Connect…", null);
try {
var res = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify({ email: email, note: note })
});
var data = {};
try { data = await res.json(); } catch (_) {}
if (res.ok && data.ok) {
var msg = "Invite requested for " + email + ". Check TestFlight / email from Apple.";
if (data.beta_review_note) msg += " " + data.beta_review_note;
if (data.join_url) msg += " Public join: " + data.join_url;
setStatus(msg, true);
} else {
setStatus((data && data.message) || ("Enrollment failed (" + res.status + "). Use the mailto fallback or try again."), false);
}
} catch (err) {
setStatus("Could not reach enrollment API. Use the mailto fallback to marchon@gmail.com.", false);
} finally {
if (submit) submit.disabled = false;
}
});
}
})();
</script>
</body>
</html>