synderesis-api / README.md
cuivienen's picture
Deploy Synderesis API Docker Space
97a66d4 verified
|
Raw
History Blame Contribute Delete
27.4 kB
---
title: Synderesis API
sdk: docker
app_port: 7860
pinned: false
license: other
---
Public Docker Space for the Synderesis FastAPI proxy.
Browser API 1.10 adds `execution_mode` (`answer` by default or `task`) on
supported Catholic LLM Synod deployments. Spark and Pro Task requests are
limited to six controller decisions and four app-executed tool calls: official
source search and, when enabled by the user, the fixed Exa web-search path.
There is no shell, arbitrary URL or file fetch, repository browser, or
controller-selected provider tool. Research remains Answer-only.
## Spark and Pro routing
Catholic chat accepts validated `model_tier` and `reasoning_effort` values.
Spark preserves the existing Synderesis experience, while Pro uses the dedicated
Synderesis premium route. Browser replies expose product names and aggregate
usage only.
The Pro provider profile keeps exact-parameter routing enabled and omits
`temperature`, `top_p`, and `seed`, which are not consistently supported across
its compatible endpoint set. It sorts healthy compatible endpoints by
throughput and retains fallback routing. Keep the request-profile regression
aligned with current endpoint metadata.
Browser chat uses a 2,048-token visible-answer target by default and accepts an
explicit target up to 4,096. OpenRouter counts hidden reasoning inside
`max_tokens`, so the server maps each product effort to the nearest explicitly
supported upstream level and expands the internal completion ceiling using the
documented reasoning share. Candidate and grounding-verifier targets remain
separately capped, quota reservation uses the same expanded values, and
High/Max calls receive longer timeouts. Do not collapse these back to the
inherited 320-token API default: that starves structured visible output,
especially at Max.
The default is Spark/Minimal. Legacy `thinkingMode` browser storage migrates to
High or Minimal. Research remains isolated and ignores these fields.
Final answers default to thorough, self-contained explanations unless the user
asks for brevity; refusals remain short.
### Catholic chat connections
The Catholic Spark and Pro workspace can advertise two optional account
connections:
- A read-only GitHub App for user-selected repositories. The App must have only
Metadata read and Contents read. The callback is
`https://www.synderesis.eu/v1/auth/github/callback`; the webhook is
`https://www.synderesis.eu/v1/integrations/github/webhook`. Configure installation,
installation-repositories, and authorization lifecycle events, use the
callback as the post-install Setup URL, and enable expiring user access
tokens.
- A bring-your-own OpenRouter key. It is saved only through the dedicated
credential endpoint and is opt-in for each browser tab and Catholic chat
request.
Both depend on a versioned AES-256-GCM keyring. Keep the following values in the
repository's age-encrypted `.env.age`; a regular Catholic LLM Synod deploy sends
non-empty values to the Space only as secrets:
```text
SYNDERESIS_CREDENTIAL_ENCRYPTION_KEYS_JSON={"v1":"<base64url-32-byte-key>"}
SYNDERESIS_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID=v1
SYNDERESIS_GITHUB_APP_CLIENT_ID=<github-app-client-id>
SYNDERESIS_GITHUB_APP_CLIENT_SECRET=<github-app-client-secret>
SYNDERESIS_GITHUB_APP_SLUG=<github-app-slug>
SYNDERESIS_GITHUB_OAUTH_CALLBACK_URL=https://www.synderesis.eu/v1/auth/github/callback
SYNDERESIS_GITHUB_WEBHOOK_SECRET=<random-webhook-secret>
```
Never delete an old keyring entry while database rows still use its key ID.
Change the active ID to rotate new writes, reconnect or re-save old credentials,
then retire the old entry. GitHub OAuth access and refresh tokens and remembered
BYOK values are encrypted in SQLite; malformed envelopes fail closed.
Session-scoped BYOK values expire within one hour and are deleted on logout.
GitHub file bytes are fetched only for the current Spark or Pro request and are
not directly stored in the database or browser-history record. Model output can
reproduce repository material, and completed assistant text can then be saved,
shared, or downloaded by the user. `--upload-only` deliberately does not read or
synchronize this secret configuration. A regular Catholic deploy removes stale
connector secrets that are absent from `.env.age`. The connector is not
advertised unless the callback is the canonical URL and the webhook secret has
at least 32 characters. A customer key may fund selected-tier Answer calls and
Task controller/final-answer model calls. The fixed web-search and suitability
review calls remain Synderesis-funded, so a request using both can have mixed
funding.
The Space installs its Python environment from the repository root `pyproject.toml`
and `uv.lock`, so local `uv sync` and the Hugging Face Docker runtime resolve the
same dependency set. The deploy bundle also contains `synderesis_deployment.json`,
which is logged as `SYNDERESIS_DEPLOYMENT_INFO` at startup and propagated to the
API diagnostics endpoint for commit/source-DB traceability.
The Space runs `scripts/serve_huggingface_api.py` under Uvicorn, downloads the official-source
SQLite retrieval database from a private Hugging Face dataset, and calls the
configured model backend server-side. Customer access is restricted by
`X-API-Key`; upstream credentials are stored only as Space secrets.
Rebuild and upload the retrieval database when deploying this version so the
compact Catechism 2357-2359 retrieval note is included in the Space artifact.
Persistent API/account state lives in SQLite under `/data/synderesis_api.sqlite3`
when the Space has the private bucket volume `cuivienen/synderesis-api-state`
mounted read-write at `/data`. The deploy script now preflights that mount before
any remote upload or Space configuration, ensures it by default after the new
bundle is uploaded, and refuses to overwrite any conflicting existing `/data`
volume; pass `--no-state-volume` only when intentionally skipping that step.
The local default SQLite journal mode remains `WAL`, but the Space sets
`SYNDERESIS_SQLITE_JOURNAL_MODE=DELETE` and the container startup path also
defaults to `DELETE`. That rollback-journal mode avoids WAL's `-wal` and `-shm`
sidecar files, which are unsafe on the NFS/FUSE-backed `/data` mount used for
durable Hugging Face bucket storage.
## Private-Beta browser chat
Keep `SYNDERESIS_SERVICE_LIVE` false during the private Beta. An account-level
`chat_access` grant opens the signed-in `/chat/` workspace and on-demand,
account-scoped server API-key management. Waitlist-only accounts remain blocked,
registration issues no key automatically, and extension-device routes stay
closed. A regular deploy writes
`SYNDERESIS_SERVICE_LIVE=0` explicitly so a stale public-launch value cannot
survive; changing that behavior is a separate launch decision.
The invitation configuration lives in the repository's age-encrypted
`.env.age`:
- `SYNDERESIS_EARLY_ACCESS_INVITE_SECRET` is a stable random value of at least
32 characters used to derive email-bound invitation codes. It is required for
waitlist invitations.
- `SYNDERESIS_ADMIN_EXPORT_KEY` authenticates the private administrator
endpoints through `X-Admin-Key`.
- `SYNDERESIS_EARLY_ACCESS_EMAILS` is an optional legacy comma-separated static
invitation list. It is not needed for the waitlist workflow.
- `SYNDERESIS_EARLY_ACCESS_MONTHLY_TOKEN_LIMIT_CUSTOMER_ID` optionally names
one generated opaque customer ID for a 1,000,000-token monthly allowance.
Its request caps remain 50/day and 1,500/month; other early-access accounts
remain at 500,000 tokens/month.
The regular deploy path decrypts these values only in memory and stores them as
Hugging Face Space **secrets**. They must never be ordinary Space variables.
An empty static email list clears only that list; a separately configured
invitation secret remains available for waitlist invitations. Removing
`SYNDERESIS_ADMIN_EXPORT_KEY` from encrypted configuration revokes a stale
deployed administrator key on the next regular secret-synchronizing deploy.
The account-specific override is strictly validated before remote mutation and
updates only existing key limits, preserving keys and usage. `--upload-only`
does not read `.env.age` or update these secrets.
Use the private administrator flow to select and prepare invitations:
```bash
curl -sS 'https://www.synderesis.eu/v1/admin/waitlist?status=pending' \
-H "X-Admin-Key: ${SYNDERESIS_ADMIN_EXPORT_KEY}"
curl -sS -X POST \
'https://www.synderesis.eu/v1/admin/beta-invitations/prepare' \
-H "X-Admin-Key: ${SYNDERESIS_ADMIN_EXPORT_KEY}" \
-H 'Content-Type: application/json' \
--data '{"customer_ids":["acct_example"]}'
```
The same administrator credential provides a private monthly usage summary:
```bash
curl -sS \
'https://www.synderesis.eu/v1/admin/usage?period=2026-07&limit=500&include_zero_usage=false' \
-H 'X-Admin-Key: <ADMIN_EXPORT_KEY>'
```
`period` must be UTC `YYYY-MM` and defaults to the current UTC month. `limit`
defaults to 500 and is bounded to 1–2000. Set `include_zero_usage=true` to add
registered accounts with no selected-month activity. The response includes
authoritative totals, matching/returned counts, and deterministic per-user
identity, classification, plan/status, request/token, endpoint, error, and
last-use fields. It intentionally excludes provider/model routing, request
content, credentials, API-key identifiers, and cost ledgers.
`SYNDERESIS_ADMIN_EXPORT_KEY` must remain a Hugging Face Space secret. Never
place a real value in documentation, logs, URLs, commits, or ordinary Space
variables.
Preparation is all-or-nothing for at most 50 active waitlist members. Its
no-store response contains each recipient's email address, private code,
subject, and exact plain-text body. Treat it as sensitive: do not log, publish,
or commit it. Send that body through the operational mailbox. The API
intentionally stores no mailbox credential, plaintext code, or message body.
Only after every selected message has actually been sent, record the exact
`invitation_version` returned by preparation:
```bash
curl -sS -X POST \
'https://www.synderesis.eu/v1/admin/beta-invitations/mark-sent' \
-H "X-Admin-Key: ${SYNDERESIS_ADMIN_EXPORT_KEY}" \
-H 'Content-Type: application/json' \
--data '{"invitations":[{"customer_id":"acct_example","invitation_version":1}]}'
```
If a secret rotation or email-binding change reissues the code, its version
increments and its sent state resets. A stale version is rejected rather than
marking the replacement code sent.
The recipient opens `/account/`, signs in with the invited address, and pastes
the code into the Beta invitation card. Never put the code in a URL or query
string. A successful claim persists `chat_access` and consumes the code while
retaining only one-way hashes. An email address alone is never authorization.
Administrators may instead issue one-time transferable registration codes:
```bash
curl -sS -X POST \
'https://www.synderesis.eu/v1/admin/beta-registration-codes' \
-H "X-Admin-Key: ${SYNDERESIS_ADMIN_EXPORT_KEY}" \
-H 'Content-Type: application/json' \
--data '{"count":2,"expires_at":"2026-08-31T23:59:59Z"}'
```
The authenticated response is marked `no-store` and is the only place the
plaintext codes appear. Deliver them out of band and do not log or retain the
response. The database stores only a SHA-256 identity, a non-secret prefix, and
lifecycle timestamps. `GET /v1/admin/beta-registration-codes` returns metadata
only; `POST /v1/admin/beta-registration-codes/{prefix}/revoke` revokes an
active unclaimed code. A code may be used by any valid new account during
registration or redeemed by a signed-in account, but exactly once. It grants
`chat_access` and never creates an API key automatically or changes
`SYNDERESIS_SERVICE_LIVE`. After claiming it, the signed-in account may create
its own bounded server API keys. Email-bound invitations above remain
account-bound and retain their existing behavior.
The optional legacy static-email path still prints each configured address's
private code once in regular-deploy JSON output. Deliver those codes with the
same precautions.
The signed-in `/chat/` endpoint accepts up to four request-scoped files of
5 MB each (5,000,000 bytes), with a 10 MB combined attachment limit. Only the
two authenticated browser-chat upload routes receive the corresponding larger
request-body budget; other API routes retain the 1 MiB body limit. Text inputs share a fair 20,000-character context
budget, capped at 16,000 characters per file, and truncation is returned in
attachment metadata for the page to disclose. Known document parsers cover PDF,
DOCX, PPTX, XLSX, ODT, EPUB, HTML/XML, and RTF. Readable plain text is also
accepted when it has no extension or arrives as `application/octet-stream`;
binary content is rejected after server-side sniffing.
### Official-source and supplemental-web controls
Catholic browser chat accepts a strict, server-enforced source policy:
```json
{
"source_policy": {
"official": "auto",
"families": ["catechism", "magisterial"],
"web_search": false,
"web_source_review": true
}
}
```
`official` accepts only `auto`, `on`, or `off`. The legacy
`retrieve_sources` Boolean remains compatible by mapping to `on` or `off`, but
a contradictory legacy/new pair is rejected. Family IDs and authority labels
come only from the committed `benchmarks/sources.json` ontology. Selecting an
empty family such as Patristics currently yields no official results; it never
falls back to an unfiltered corpus search. Retrieved official entries are
labelled as verbatim source text, curated source notes, or registry metadata.
Neither the answering model nor a web-review model may assign official status,
orthodoxy, imprimatur, dogmatic rank, or doctrinal authority.
Web search is default-off and available only for the Catholic `llm-synod`
browser workspace when the existing OpenRouter secret is configured. After the
browser request reserves quota, ordinary Answer mode makes one fixed Exa-backed
search using only a whitespace-normalized, 2,000-character maximum copy of the
current message. Task mode may make up to four such searches, with bounded
queries derived from the request and prior Task observations. Neither mode adds
history, individual or organizational memory, attachments, uploaded filenames
or content, file/citation locations, or device/geolocation context to a search.
The controller cannot select arbitrary provider tools or fetch arbitrary URLs or
files. Each search accepts at most five results and at most 2,000 characters per
result; the final deduplicated web-evidence set for the request is capped at
five. Only documented `url_citation` annotations are parsed; assistant prose is
ignored for source discovery. The server never follows those URLs. It rejects
non-HTTPS, credential-bearing, private/internal/reserved-host, and
signed/authenticated URLs, strips fragments, deduplicates URLs, and bounds all
excerpts.
When `web_source_review` is enabled (the default), a separate strict-JSON GLM
pass reviews each result batch using only its minimized search query plus opaque
citation ID, title, domain, and bounded excerpt. It filters relevance, evidence
usefulness, source genre, spam, manipulation, and likely prompt injection.
Missing, malformed, uncertain, or excluded decisions fail closed. Turning
review off skips only this suitability pass; it does not weaken deterministic
URL, annotation, size, or citation-key validation.
Accepted web excerpts remain untrusted supplemental evidence and cannot
override the official Catholic corpus. Answers may cite only exact issued
`[[w1]]` through `[[w5]]` markers. Browser replies retain the legacy source and
document fields and add a trust-separated `citation_manifest` with official,
web, and uploaded-document entries. Web entries include bounded title, public
URL, domain, excerpt, UTC access time, excerpt hash, and review status/reason;
the search query, engine/model internals, credentials, and unbounded content are
not returned. Discovery, optional review, and generation share one quota
reservation and one usage event, including partial usage on a failed review.
Web search and review remain Synderesis-funded when a customer OpenRouter key
funds selected-tier Answer or Task controller/final-answer calls; such a request
reports mixed funding.
Web evidence is server-side request-scoped and is not written to server
conversation history. Only the bounded public manifest for sources actually
cited may remain in account-scoped browser `localStorage`; uploaded-document
citations remain transient. OpenRouter and Exa receive the ordinary Answer
query or a bounded Task query derived from the request and prior Task
observations, so their retention is governed by the configured provider account
and terms. They do not receive appended history, memory, files, or device
context from the search feature. The deployment's explicit question/answer
logging flags remain a separate retention choice. Research bundles advertise
both official retrieval and web search as unavailable and make neither source
call.
When the configured backend is the tested LLM Synod path, bounded decoder
validation allows PNG, JPEG, GIF, and WebP. Image data URLs go only to validated
visual advisers. Text-only advisers and the final synthesis/review stages
receive bounded text context, candidate drafts, and successful visual-witness
summaries. Other backends report image input as unavailable and reject images
rather than silently ignoring them. PDFs use text extraction, not OCR.
## GitHub automatic deployment
`.github/workflows/deploy-huggingface-space.yml` runs for pushes to `main` and
from the **Run workflow** manual control. Create a GitHub Actions environment
named `production` and add its sole required secret, `HF_TOKEN`, with permission
to update the existing Space. Concurrent runs are serialized and an active
deployment is not cancelled.
CI calls `scripts/deploy_huggingface_space.py --upload-only`. It builds and
uploads the bundle from the checked-out commit, preflights `/data`, and ensures
the existing state volume after upload. It never decrypts `.env.age`, uploads
the source database, reconfigures Space variables or secrets, or generates or
rotates bootstrap customer keys.
For an application rollback, revert the commit on `main` or manually dispatch
the workflow for the desired revision. Persistent `/data` contents are not
rolled back. Disable the workflow in GitHub Actions to stop automatic deploys;
removing the production environment's `HF_TOKEN` also makes runs fail before
upload.
Supported backend values are `llm-synod` and `tinker`; `llm-synod` is the default
for the deployed Space. The Synderesis Spark LLM Synod route uses MiniMax M3 and
DeepSeek V4 Pro as one parallel low-cost OpenRouter adviser wave. MiniMax is
pinned to the cheapest current GMICloud endpoint, which was verified for visible
JSON and image output. DeepSeek is pinned to StreamLake and omits `seed`, which
that endpoint does not support; the OpenRouter account's privacy guardrails
continue to exclude the cheaper first-party DeepSeek route. Qwen3.7 Max is the
first quorum rescue, with low-reasoning Grok 4.5 reserved for a failed Qwen
rescue, two failed primaries, or missing visual evidence. Unavoidably paired
Qwen and Grok rescues start in parallel. Non-reasoning GLM 5.2 is pinned to
CoreWeave and is the final source-constrained Catholic
synthesizer/gatekeeper. Muse Spark 1.1 is excluded because its OpenRouter route
is restricted to US users. The 2026-07-28 review retained only endpoints that
passed bounded live compatibility checks; advertised routes that returned
400/404 were rejected. Responses report whether escalation was used, while
quota checks reserve four adviser calls plus synthesis, repair, and up to two
semantic grounding-verifier calls. Routing keeps parameter support mandatory.
Qwen and Grok use soft latency/throughput preferences without replacing the
default price-aware ordering. It does not route GPT, Claude, or Gemini models
through OpenRouter. After exact citation-pair validation, a semantic verifier
rejects any answer or qualification that contradicts a controlling source note,
reverses its teaching, attributes unsupported content to it, or overstates what
it settles. The verifier accepts only server-resolved curated notes or exact
official document text; registry-only metadata and user-supplied text are not
verification evidence. One repaired answer is verified again. Signed-in browser
chat, paid `/v1/answers`, and public `/v1/demo/answers` return the latest
schema-valid, structurally safe answer with advisory metadata when semantic
review remains rejected or retrieved official references go uncited. Malformed
or schema-invalid output, missing citation identifiers, and invented or
mismatched exact official citation pairs remain withheld. Every request retains
request/token metering, quota accounting, and private provider-cost accounting
for all candidate, synthesizer, repair, and verifier tokens already spent.
Stripe overage is enqueued only for a final delivered `accepted` or `advisory`
gatekeeper; withheld, missing, unknown, and failed outcomes remain nonbillable.
Consumers should use the gatekeeper status and
`grounding_status.delivery_status` as the release decision; strict diagnostic
fields remain false for advisory cases.
The uploaded variant is mutually exclusive and its bundle metadata is
authoritative for both UI and server mode: a Catholic bundle rejects a
caller-supplied research workspace, while a research bundle rejects Catholic
chat, before quota or provider calls.
Research requests call canonical `gpt-5.6-sol` at the direct OpenAI Responses API
with validated image inputs, high reasoning, bounded output, and `store: false`;
canonical `claude-opus-5` then synthesizes strict JSON at the direct Anthropic
Messages API and may make exactly one repair call. Both hosts and model IDs are
pinned, and GPT/Claude are never sent through OpenRouter.
Missing `SYNDERESIS_RESEARCH_OPENAI_API_KEY` or
`SYNDERESIS_RESEARCH_ANTHROPIC_API_KEY` fails only enabled research requests;
the generic provider names remain accepted as local input aliases. A regular
`--website-variant research` deploy that synchronizes secrets requires both keys
from process memory or decrypted `.env.age` and stores the scoped names only as
Space secrets. It does not require or install `OPENROUTER_API_KEY`. Catholic
deploys and `--no-set-secrets` never resolve, update, or clear the direct keys;
therefore research `--upload-only`/`--no-set-secrets` requires both
direct-provider secrets to be preinstalled (scoped names preferred; generic
aliases accepted). The public composite model is reported as
`synderesis-v3-research-sol-opus`. Token usage and published model-price estimates
from both direct providers—including a completed paid stage before a later
provider failure—enter the existing quota contract. Exact uploaded-document
`[[dN]]` validation remains unchanged.
Canonical model routes belong only in Hugging Face Space variables. Every
regular secret-synchronizing deploy removes all known model-route names from the
Space secret namespace, including active route names and stale HF/Tinker
aliases, so a misplaced secret cannot shadow the authoritative variable.
The launcher supplies its built-in Tinker model path only when the effective
backend is `tinker`; an explicit path remains untouched so wrong-backend drift
still fails closed during runtime validation. Full non-Tinker secret sync also
removes a stale `TINKER_API_KEY`, while Tinker sync installs it and every backend
retains `HF_TOKEN` for Hub dataset access.
`--no-set-secrets` and `--upload-only` leave the complete secret namespace
unchanged.
The website links a structured-answer citation only when its exact source ID and
location match a retrieved passage. Matched links use human-readable source titles
in separated list items. Citation navigation shows document-chunk text as the
relevant passage and separately labels any summary. Curated retrieval-note rows
are labelled as notes and are not presented as quotations. Registry-only rows
are labelled as metadata rather than evidence. The renderer omits
non-HTTP(S) or non-official external links.
## Website variant preparation
The existing Catholic product remains the default compatibility mode:
```bash
python scripts/deploy_huggingface_space.py --prepare-only --website-variant catholic
```
Prepare the replacement-ready research landing page and workspace overlay with:
```bash
python scripts/deploy_huggingface_space.py --prepare-only --website-variant research
```
Both variants target the existing Hugging Face Space, so deployment retains the
`https://www.synderesis.eu` canonical public origin. The research selection copies the complete shared
website and replaces only its landing and chat HTML; the controller, styles,
account/session gate, static assets, and citation renderer remain shared.
Rollback consists of rebuilding and deploying with `--website-variant catholic`.
Uploaded files, filenames, extracted text, and document-citation metadata remain
request-scoped and are not stored in browser history. This thin variant adds no
database, background job system, provider stack, or online literature-search
capability. It does not deploy either variant by itself.
# Optional Stripe billing
When enabled, set the Stripe secret key and webhook secret as Space secrets. Set both price IDs, meter event name, public origin, included retail micro-USD, matching fixed monthly cents, expected livemode, and `SYNDERESIS_STRIPE_REQUIRE_PERSISTENT_LEDGER=true` as Space variables. `SYNDERESIS_STRIPE_BYOK_PLATFORM_FEE_RATE` is an optional exact-decimal Space variable that defaults to `0.25`; it is rejected when orphaned from the complete Stripe configuration. When the authoritative encrypted configuration omits it, a regular deploy replaces any stale custom value with the explicit `0.25` default. Any live-mode API ledger, including direct runtime configuration, must resolve below `/data`; startup fails rather than silently using ephemeral storage. The Stripe client is pinned to `2026-06-24.dahlia`, and startup validates the complete two-price catalog and Meter mapping. Stripe billing is supported only with the `llm-synod` backend because it is the only backend with a trusted provider-cost ledger; deployment rejects complete Stripe wiring for every other backend before making remote changes.
Keep `SYNDERESIS_SERVICE_LIVE` false for normal private-Beta deploys even when Stripe is configured. A paid entitlement may open browser chat, but it never opens API-key management or device/extension routes; invitation registration and signed-in claims, waitlist membership, administrator endpoints, and complimentary nonmetered Beta access continue unchanged. Runtime managed retail uses the fixed true 50% provider-cost contribution margin. BYOK uses zero Synderesis provider cost plus the configured platform fee on trusted provider-reported cost, or on exact committed price multiplied by provider-reported tokens when cost is absent; it never uses character estimates, unknown-model prices, or the 1.055 loading. The tier-specific managed alternatives in `docs/pricing-margin-analysis.md` are recommendations only.