ssc-app
The tiny helper an app installs to read the SSC identity note (X-SSC-Identity), to reconnect streams before the gateway's limit, to keep files and to read company data.
pip install ssc-app
Python 3.11 or later. Depends on pydantic (2.5 or later) and pyjwt[crypto] (2.15 or later) only, both as ranges, so the app keeps its own pins.
import os
from ssc_app.identity import IdentityRefused, IdentityVerifier
verifier = IdentityVerifier(
audience=os.environ["SSC_APP_ORIGIN"], # this app's exact origin, preview's included
keys=os.environ["SSC_IDENTITY_KEYS_URL"], # data: URL in a cell; else JWKS URL or dict
)
note = verifier.from_headers(request.headers)
note.sub # "usr_…" or "sch_…": key on this
note.name # display only; None for a schedule run
note.email # display only; None for a schedule run
SSC_APP_ORIGIN is the app's exact origin, such as https://expenses.<cell label>.delimitusapps.com (https://expenses--preview.<cell label>.delimitusapps.com in preview). In SSC, SSC_IDENTITY_KEYS_URL holds the signing keys as a data: URL, read without a network call; code outside SSC may pass https://keys.delimitus.com/<cell label>/jwks.json instead, fetched once and cached.
The note is an ES256 JWT (typ: ssc-id+jwt) that lasts five minutes. Key on sub, never on email: email and name are whatever the company directory says today, and a schedule run has neither. Verify once per request; never re-verify inside a long WebSocket or event stream. Every refusal is IdentityRefused with a code; treat them all as "not a signed-in user of this app".
Reconnect before the limit
Each WebSocket or event stream ends at the limit the gateway reports (5 minutes for most apps, 60 for session apps). ssc_app.reconnect reads the gateway's X-SSC-Request-Deadline header and ends the stream a little before then; the browser reconnects without a reload. No extra dependency.
from ssc_app.reconnect import browser_client, close_before_deadline, end_before_deadline
# Event stream: EventSource reconnects by itself, with Last-Event-ID if your events carry an id.
return StreamingResponse(
end_before_deadline(events(), request.headers), media_type="text/event-stream"
)
# WebSocket: closed with code 1012, 30 seconds before the limit.
await ws.accept()
restart = asyncio.create_task(close_before_deadline(ws.close, ws.headers))
try:
...
finally:
restart.cancel()
# The browser side: serve browser_client(), then sscSocket(url, {onopen, onmessage}) instead of new WebSocket(url).
seconds_left(request.headers) gives the seconds until the limit, or None without the gateway. Options: margin (30 s) on both, retry_ms (1000) on end_before_deadline. A stream that opens with less than the margin left is ended after half the time left (seconds_to_wait), never at once. A reconnect is a new connection: keep what must outlive it in Postgres or in the browser. The Node helper @delimitus/ssc-reconnect has the same names and is tested against the same shared vectors.
Files
Ask for file storage with [files] in ssc.toml; the first deploy that asks sets it up once, in a few minutes. ssc_app.files asks the cell's data gateway for a signed link with the app's own ID token and sends the bytes to Cloud Storage; the app holds no storage credentials. No extra dependency.
from ssc_app import files
files.put("photos/cat.png", data, content_type="image/png")
data = files.get("photos/cat.png")
url = files.link("get", "photos/cat.png")["url"] # redirect a browser here to download
files.delete("photos/cat.png")
Each environment has its own files: at most 25 MB each and 1 GB together. A name is /-separated segments of A-Z a-z 0-9 . _ -, each starting with a letter or digit. A link lasts 10 minutes, and a download always arrives as an attachment, never shown as a page. Upload from the server: the bucket allows no browser-direct upload. Files are not scanned for viruses. Errors are FilesError with the data gateway's code (FILE_NOT_FOUND, FILES_QUOTA_EXCEEDED, APP_NOT_ACTIVE, ...), STORAGE_<status>, FILE_TOO_LARGE (over the link's max_bytes, refused before anything is sent), UNREACHABLE (the gateway, Cloud Storage or the app's own ID token could not be had) or BAD_GATEWAY_RESPONSE (an answer that is not JSON); the data gateway starts from zero, so a call to it is tried once more on a timeout, a lost connection or a 502, 503 or 504. Delete is idempotent: a retried delete that finds nothing succeeds. The gateway's address comes from the metadata server (SSC_DATAGW_URL replaces it). The Node helper @delimitus/ssc-files has the same names, with remove for delete.
Company data
Name a connection in [connections] names in ssc.toml; the environment is linked to the connection (by its preview access rule, or by an admin or an approval). ssc_app.data.query sends one read-only statement to the cell's data gateway with the app's own ID token; the app holds no database credentials. No extra dependency. For a postgres connection:
from ssc_app import data
result = data.query("finance", "select id, total from invoices where year = $1", [2026])
for row in result.rows: # a list in column order
...
schema = data.describe("finance") # describe(name) -> Schema(connection, kind, tables)
for table in schema.tables: # Table(name, columns), each column {name, type, db_type}
...
data.describe(name) names the tables of a granted connection and their columns, typed as a query types them (none for a file not yet read); the data gateway keeps a description five minutes, and a refusal is a DataError as for a query.
The statement follows the connection's kind: one SELECT for postgres ($1, $2, ... placeholders) and for mysql, sqlserver, bigquery and snowflake (? placeholders); and, with no parameters, an A1 range for gsheets, list <prefix> or get <key> for s3 and gcs, <table>[ view <view>][ where <formula>] for airtable and a GET path for rest. max_rows, max_bytes and timeout_ms only narrow what the platform, the connection and the grant allow; result.truncated and result.truncated_reason say the gateway stopped reading early. To act for the signed-in user pass the request's X-SSC-Identity value as identity=; without it the app acts for itself. A decimal is a string and a timestamp is ISO 8601. Errors are DataError with the data gateway's code (CONNECTION_NOT_GRANTED, CONNECTION_SUSPENDED, QUERY_REFUSED with a reason (other_database, not_read_only, multiple_statements, unsupported_statement), QUERY_FAILED with a sqlstate, DAILY_BUDGET_SPENT, ...), UNREACHABLE (the gateway or the app's own ID token could not be had) or BAD_GATEWAY_RESPONSE (an answer that is not JSON); a query is tried once more on a lost connection or a 502, 503 or 504, and one that timed out is not. The Node helper @delimitus/ssc-data has the same names with maxRows, maxBytes and timeoutMs.
Metadata
Release files for ssc-app 0.0.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ssc_app-0.0.5.tar.gz | 18.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ssc_app-0.0.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.4 kB
Release files / ssc_app-0.0.5.tar.gz
| Download URL | ssc_app-0.0.5.tar.gz |
|---|---|
| Size | 18.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
33f1b552c21adc089147349ea96e29e900f83d09862f9528dd65cecb4f72c307
|
|
BLAKE2b-256 checksum How to use checksums |
65a7ede24ffc7f4d6f073ae34418feb96b8e0e0db345cb78b7d1e51d4028994b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / ssc_app-0.0.5-py3-none-any.whl
| Download URL | ssc_app-0.0.5-py3-none-any.whl |
|---|---|
| Size | 24.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eb8ae032b78e992280b61c09709f5bc6219a3696a2123957f55748c2f6421f8a
|
|
BLAKE2b-256 checksum How to use checksums |
122dc44076c60041605ba2753033859e5e5f196cca1f236d92385978c7d41880
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for ssc-app 0.0.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for ssc_app-0.0.5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.