synderesis-api / README.md
cuivienen's picture
Deploy Synderesis API Docker Space
97a66d4 verified
|
Raw
History Blame Contribute Delete
27.4 kB
metadata
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:

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:

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.