From the Assure repository at 262612b1426d, rendered as written. Source · Markdown

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_KEY

A 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 HEAD

The 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.exit is the exit code a CI job should use, and policy.reason says why. nothing_disproven is exit 0 and colour green: nothing is disproven within the bounds the first line states. disproven is exit 1, red. could_not_look is 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/v1 with outcome, reason and action.

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.