scribase

Platform

Production control API

The authenticated /v1 management API over the durable Postgres executor.

scribase-control-api serve runs the authenticated HTTP/1.1 management API over the durable PostgreSQL executor. It defaults to 127.0.0.1:8080; terminate TLS at a trusted ingress or same-host sidecar. A non-loopback bind is rejected unless SCRIBASE_TRUST_PROXY_TLS=1 explicitly acknowledges that deployment boundary.

Required configuration:

Variable Purpose
SCRIBASE_API_DATABASE_URL TLS URL using the least-privilege scribase_api role
SCRIBASE_AUTH_MODE oidc (default; the five values below are then required) or builtin (the control plane's own password sign-in, no IdP; see DEPLOY.md "Operator sign-in")
SCRIBASE_OIDC_INTROSPECTION_ENDPOINT HTTPS RFC 7662 token-introspection endpoint
SCRIBASE_OIDC_ISSUER Exact trusted iss claim, including any trailing slash
SCRIBASE_OIDC_AUDIENCE Required API audience in the aud claim
SCRIBASE_OIDC_CLIENT_ID Confidential introspection client identifier
SCRIBASE_OIDC_CLIENT_SECRET Confidential introspection client secret

The database URL must contain sslmode=require. PostgreSQL 16+ psql, curl, and jq must be installed. The introspection request and its Basic client credential are streamed to curl over stdin and never appear in arguments, debug output, or error messages. A token is accepted only when introspection reports it active with the exact configured issuer and audience, a future numeric expiry, an optional valid not-before time, and a bounded subject. Organization roles are then loaded from current forced-RLS membership state; token metadata is not used for authorization. The API process is never given the scribase_operator or schema-migrator database credential.

Optional configuration:

Variable Default
SCRIBASE_HTTP_BIND 127.0.0.1:8080
SCRIBASE_TRUST_PROXY_TLS unset; required as 1 for non-loopback binding
SCRIBASE_MAX_ENVIRONMENTS 10 for newly bootstrapped organizations
SCRIBASE_DATABASE_MAX_CONNECTIONS 10
SCRIBASE_HTTP_READ_TIMEOUT_SECONDS 15
SCRIBASE_HTTP_WRITE_TIMEOUT_SECONDS 15
SCRIBASE_HTTP_MAXIMUM_HEADER_BYTES 32768
SCRIBASE_HTTP_MAXIMUM_BODY_BYTES 1048576
SCRIBASE_HTTP_WORKER_THREADS 4
SCRIBASE_HTTP_MAXIMUM_QUEUED_CONNECTIONS 64
SCRIBASE_HTTP_KEEP_ALIVE_SECONDS 5 (idle time between requests on one connection; 0 closes after every response)
SCRIBASE_HTTP_MAXIMUM_REQUESTS_PER_CONNECTION 1000
SCRIBASE_PSQL_BINARY psql
SCRIBASE_DB_DRIVER pool (default when unset or empty): adapter database sessions and the per-request membership check use an in-process driver pool (ADR 0001, SX-D1), so SQL goes over the wire protocol and no psql process starts. psql is the rollback: one psql process per session and the core executor for membership. URLs with client certificates or a service file keep using psql. An unknown value keeps the pool and is logged at start.
SCRIBASE_DB_POOL_MAX_IDLE_PER_DATABASE 8 (idle pooled connections kept per database URL, at most 64)
SCRIBASE_DB_POOL_MAX_IDLE 64 (idle pooled connections kept in total, at most 1024)
SCRIBASE_CURL_BINARY curl
SCRIBASE_JQ_BINARY jq

The adapter rejects transfer encoding, duplicate headers, request pipelining, unknown JSON fields, oversized input, percent-encoded management paths, and unsupported content types. Responses disable caching and MIME sniffing. The operation lookup endpoint requires X-Scribase-Organization-ID so membership is resolved before accessing an operation identifier.

Scribase Mail (transactional email)

Sent directly over SMTP (OCI Email Delivery) when SCRIBASE_MAIL_SMTP_HOST is set (_PORT, _USER, _PASS; SCRIBASE_MAIL_APPROVED_SENDERS, SCRIBASE_MAIL_PRODUCT_DOMAINS and SCRIBASE_MAIL_INTERNAL_DAILY_CAP have defaults). Tables: migration 0100.

Route Contract
POST /emails, GET /emails/{id} Resend: from, to, cc, bcc, reply_to, subject, html, text, tags, headers; Idempotency-Key header; 200 {"id"}; errors {"statusCode","name","message"}
POST /api/rally/v1/transactional/send Rally Send: {product, project, to, from_local, from_name, subject, html, text, idempotency_key, unsubscribe_url, tags}; From is <from_local>@<product mail domain>; 202 {"id"}, 401, 403 (other product), 409, 422 (suppressed or unapproved sender), 429 (daily cap), 503

Both take Authorization: Bearer smail_.... A key is scoped to sender addresses or domains, and the From address must also be an approved sender. Each send checks suppressions, claims its idempotency key (a retry never sends twice; a failed send is retried under the same id), and takes the per-key, per-project daily cap. App email (.../email/send) uses the same path, with Rally Send kept only as the fallback when SMTP is not set.

Keys (printed once, as env lines; only the SHA-256 is stored):

docker exec scribase-control-api-1 scribase-control-api mail-key mint \
  --product menivor --senders mail.menivor.com [--label web] [--daily-cap 2000]
docker exec scribase-control-api-1 scribase-control-api mail-key list
docker exec scribase-control-api-1 scribase-control-api mail-key revoke mk_...