Spaces:
Running
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 ishttps://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:
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_SECRETis 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_KEYauthenticates the private administrator endpoints throughX-Admin-Key.SYNDERESIS_EARLY_ACCESS_EMAILSis an optional legacy comma-separated static invitation list. It is not needed for the waitlist workflow.SYNDERESIS_EARLY_ACCESS_MONTHLY_TOKEN_LIMIT_CUSTOMER_IDoptionally 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:
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:
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:
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:
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:
{
"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:
python scripts/deploy_huggingface_space.py --prepare-only --website-variant catholic
Prepare the replacement-ready research landing page and workspace overlay with:
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.