Web app and API¶
corpusos web serves three things from one process:
| Path | What |
|---|---|
/ |
the web app: Search, Library, Tables, and for admins Storage, Review and Jobs |
/api/… |
the HTTP API; interactive reference at /api/reference |
/docs/ |
this documentation, when the site is built (CORPUSOS_DOCS_DIR) |
/health |
liveness check (/healthz too, except on Cloud Run, which reserves it) |
corpusos web --host 0.0.0.0 --port 8080 -C /path/to/corpus # PORT is read from the environment too
Sign in¶
Set web.auth in config/security.yaml, or the CORPUSOS_WEB_AUTH environment variable.
| Mode | Use it for | How it works |
|---|---|---|
header |
local development | Trusts the X-CorpusOS-Principal header. Refused on anything but loopback. |
firebase |
Google, Microsoft, email or SAML sign-in through Firebase Auth or Google Identity Platform | The browser signs in with the Firebase SDK. The server exchanges the fresh ID token for an HttpOnly session cookie and verifies that cookie on every request, including the revocation check. |
jwt |
an authenticating proxy (Google IAP, oauth2-proxy, an API gateway) | Verifies the proxy's signed token against the issuer's JWKS, issuer and audience. |
web:
auth: firebase
public_url: https://corpus.example.com
admins: ${CORPUSOS_ADMINS} # comma-separated emails
allowed_domains: example.com # who may sign in at all; empty = any verified email
allowed_emails: ""
principal_map: {alice@example.com: [finance]} # email -> extra principals (groups)
firebase:
project_id: ${CORPUSOS_FIREBASE_PROJECT_ID}
api_key: ${CORPUSOS_FIREBASE_API_KEY} # the public web API key: it identifies the project, grants nothing
auth_domain: ${CORPUSOS_FIREBASE_AUTH_DOMAIN}
tenant_id: "" # Identity Platform tenant, if you use one
session_days: 5
For Firebase:
- Add your site's domain to Authentication › Settings › Authorized domains.
- Give the server's service account permission to create session cookies and read users, for example
roles/firebaseauth.adminon the Firebase project. - Install the
firebaseextra.
For Google IAP:
web:
auth: jwt
jwt:
header: x-goog-iap-jwt-assertion
issuer: https://cloud.google.com/iap
audience: /projects/<project-number>/locations/<region>/services/<service>
jwks_url: https://www.gstatic.com/iap/verify/public_key-jwk
algorithms: [ES256]
principal_claim: email
Your principals are your verified email plus anything principal_map adds. Every read is checked against each document's live access list. Being an admin adds upload, delete, sync and review rights, but no extra read rights.
Cross-site request protection
Session cookies are HttpOnly, Secure and SameSite=Strict. Any write made with a cookie must also send the header X-CorpusOS-Request: 1. The web app sends it for you.
The API¶
For scripts: API keys¶
corpusos mcp-key create -p me@example.com --label uploader # prints the key once, plus its SHA-256
web:
api_keys:
- {label: uploader, sha256: <sha256>, principals: [me@example.com], admin: true}
Send it as X-API-Key: <key> or Authorization: Bearer <key>. Only the hash is stored. In firebase mode, a script can also send a Firebase ID token as Authorization: Bearer <id-token>.
Reading¶
| Method and path | Returns |
|---|---|
GET /api/search?q=…&limit=8 |
routed search: passages with citations and an evidence_status of sufficient or insufficient |
GET /api/library |
the current documents you may read |
GET /api/documents/{document_id} |
the canonical document |
GET /api/evidence/{chunk_id}?expand=section |
a passage with its section, or with its neighbours |
GET /api/documents/{id}/compare |
what changed between revisions |
GET /api/lineage/{id} |
how a document or passage was produced |
GET /api/tables · POST /api/sql |
typed tables and read-only SQL |
GET /api/me |
who you are signed in as |
Writing (admins)¶
| Method and path | Does |
|---|---|
POST /api/storage/{source} |
uploads files (multipart), with an optional paths per file to keep folders, then queues a sync |
DELETE /api/storage/{source}/{path} |
removes a file from storage; the sync then deletes it from the corpus |
POST /api/sync?source=… |
ingests one source folder, or all of them |
GET /api/storage · GET /api/jobs · GET /api/jobs/{id} |
storage contents and ingest jobs |
GET /api/review · GET /api/review/{rev}/content |
the review queue and held content |
POST /api/review/{rev}/approve · /reject |
publishes a held revision, or keeps it out for good |
GET /api/admin/status |
corpus counts |
KEY=cos_...
curl -H "X-API-Key: $KEY" -F files=@handbook.pdf -F files=@budget.xlsx https://corpus.example.com/api/storage/hr
curl -H "X-API-Key: $KEY" https://corpus.example.com/api/jobs/job_1a2b3c4d5e6f
curl -H "X-API-Key: $KEY" "https://corpus.example.com/api/search?q=parental+leave"
Writes return 202 together with a job. Ingest runs in the background, one job at a time, so a long OCR run doesn't hold a request open.