Assure API: first run on one page
This page takes you to a first verdict from the HTTP API with the postgresql-observed-baseline profile, which needs no declaration. The full reference is QUICKSTART-API.md.
Base URL: https://api.symbolia.ai
The API runs the checker on Symbolia's server. You send collected files. You get back a verdict.
1. Get a key
Symbolia issues keys by hand. Ask Symbolia for a key, and you receive it once over a private channel. Symbolia keeps only a hash of it. If you lose it, ask for a new one; the old one is revoked.
Keep the key in an environment variable. The examples below read it from $ASSURE_API_KEY. Send it on every request except /healthz:
Authorization: Bearer $ASSURE_API_KEYA key expires 30 days after it is issued. A key past its expiry is refused key_expired: ask Symbolia for a new key.
From a GitHub Actions workflow you need no stored key. QUICKSTART-API.md, "1. Get a key", describes the sign-in with the workflow's GitHub identity.
2. Collect
The API never connects to your database. You collect the files first, on a machine that can reach the server.
The collector is action/collector/collect_pg.py in the public Symbolia-Assurance/assure-action repository. Fetch it from the beta branch:
git clone https://github.com/Symbolia-Assurance/assure-action assure-action
git -C assure-action checkout beta
git -C assure-action rev-parse HEADThe last command prints the full 40-character SHA of the commit you fetched. Record it with your results; to collect at the same commit again, check out that SHA in place of beta.
Create the collection role as in QUICKSTART-ACTION.md, "2. Create a collection role". Set the standard libpq variables (PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE), then run, from assure-action/action/collector/:
python3.14 -B collect_pg.py --out out \
--role assure_collector \
--privileges pg_read_all_settings,pg_read_all_stats \
--data-dir /var/lib/postgresql/18/main--data-dir must be a directory this machine can read. If the server keeps its configuration files outside the data directory (the Debian layout uses /etc/postgresql/<major>/<cluster>), add --extra-root <that directory>, once per directory. Exit 0 or 1 means the output is ready to send (1: with gaps the sidecar names).
3. Input files
Send only the files the profile reads. GET /v1/profiles lists, for each profile, the required names, the accepted names and patterns, and each one's size limit. postgresql-observed-baseline requires raw/catalog_snapshot.json, and its check must also observe the server's major version, so send raw/COLLECTION-SIDECAR.json from the same collection. Without it the check is refused profile_refused with detail.kind major_not_observed, and that refusal is charged.
Gather the collector's output into one raw/ folder:
mkdir -p bundle/raw
cp -R out/raw/. bundle/raw/
cp out/COLLECTION-SIDECAR.json out/TIMING.json bundle/raw/Send the collector's own output from one run, never a sidecar kept from an earlier run. Do not send REDACTION-MANIFEST.json; it is not an accepted name.
4. Submit a check
POST /v1/checks takes a JSON body of two keys, profile and files, where each file is base64. This builds the body from bundle/ with standard tools:
PROFILE=postgresql-observed-baseline
cd bundle
{
printf '{"profile":"%s","files":{' "$PROFILE"
sep=''
find raw -type f | LC_ALL=C sort | while IFS= read -r name; do
printf '%s"%s":"%s"' "$sep" "$name" "$(base64 < "$name" | tr -d '\n')"
sep=','
done
printf '}}'
} > ../request.json
cd ..Send request.json:
curl -sS https://api.symbolia.ai/v1/checks \
-H "Authorization: Bearer $ASSURE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(openssl rand -hex 16)" \
--data-binary @request.json \
-o response.json -w '%{http_code}\n'The server needs Content-Length; --data-binary @file sets it. Chunked bodies are refused. Make one new random Idempotency-Key for each check, and send the same value again only when you resend that same request, for example after a lost connection.
5. Read the answer
- 200: the check finished. The body is the verdict, schema
assure.serve.verdict/v1.policy.exitis the exit code a CI job should use, andpolicy.reasonsays why.nothing_disprovenis exit 0 andcolourgreen: nothing is disproven within the bounds the first line states.disprovenis exit 1, red.could_not_lookis exit 3, yellow: the check was stopped or no machine was read. The first line names what was not read; VERDICTS.md, "9. The policy exit", gives it word for word. - 202: the check is still running 20 seconds after the API received the request (its upload and admission count in those 20 seconds; when they take longer than 19.5 seconds, the check is given half a second after its admission before the 202). The body is
{"check_id": "…", "status": "running"}. Poll for it every few seconds:
curl -sS https://api.symbolia.ai/v1/checks/<check_id> \
-H "Authorization: Bearer $ASSURE_API_KEY"A running check answers 202 again. A finished check answers 200 with the verdict.
- Any other code: a typed outcome (QUICKSTART-API.md, "9. Typed outcomes"). The body is schema
assure.serve.failure/v1withoutcome,reasonandaction.
The verdict stays readable at GET /v1/checks/<check_id> for 30 days. Only your account can read it.
What is sent
Your files are written to a scratch folder for one check, and the folder is deleted when the check ends. On the Premium tier a sealed copy is kept for the formal shadow (see DATA.md). The verdict is kept 30 days. Details are in DATA.md. What a verdict claims is in SCOPE.md.