Skip to content

Getting started

Opaline evaluates a versioned rule pack against a set of facts and returns a decision. This page gets you from a key to a decision.

Before you start

When we onboard your tenant, you receive a master key (opl_admin_…). You use it once to mint your own data-plane keys; those keys make decisions and publish packs. New to the model? Read Concepts first. It defines pack, facts, status, and confidence.

The API is hosted at https://api.opaline.dev.

Authentication

Every authenticated request carries Authorization: Bearer <key>. There are three key kinds:

PrefixKindUse
opl_admin_masterYour tenant master key. Mint and revoke your own data-plane keys via /v1/keys. Issued at onboarding; scoped to your tenant.
opl_live_dataRuntime operations on the live namespace (metered).
opl_test_dataRuntime operations on the test namespace (unmetered).

Unauthenticated endpoints: GET /healthz, GET /metrics, GET /v1/schemas/pack, GET /v1/tools.

Mint a data-plane key

Use your master key to mint a data-plane key with the scopes it needs. The key is shown once, so store it.

bash
curl -X POST "https://api.opaline.dev/v1/keys" \
  -H "Authorization: Bearer opl_admin_…" \
  -H "Content-Type: application/json" \
  -d '{ "scopes": ["decide", "simulate", "manage_packs", "pack_publish"] }'
# → { "id": "…", "key": "opl_live_…" }

A runtime key that only makes decisions needs just ["decide"]. Add is_test: true to mint an unmetered opl_test_ key. Revoke a key any time with DELETE /v1/keys/{id} (master key).

Your first decision

A decision requires an Idempotency-Key (a unique value per logical decision; a UUID per request is typical). Retrying with the same key and body replays the stored response and never re-evaluates or re-meters.

bash
curl -X POST "https://api.opaline.dev/v1/decide" \
  -H "Authorization: Bearer opl_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "pack": "acme.bill.validity",
    "version": "1.2.0",
    "facts": { "vat_rate": "0.1", "total": "1250.00" },
    "context": { "transaction_date": "2026-06-12" }
  }'

A decision looks like:

json
{
  "status": "PASS",
  "score": "1",
  "auto_clearable": true,
  "rules": [
    { "rule_id": "vat_rate_check", "status": "PASS", "confidence": "1", "severity": "soft" }
  ]
}

The overall status is PASS, WARN, or FAIL. A missing or unevaluable input makes the affected rule INCONCLUSIVE and lists it in the response's manifests, never a silent skip; a hard inconclusive rule caps the decision at WARN. Numbers are decimal strings ("0.1", not 0.1). See Concepts for the full model.

Publish a pack

Publish is immutable and runs the full gate (parse, lint, AOT compile, embedded tests) before accepting. The key needs the pack_publish scope. See Pack authoring.

bash
curl -X POST "https://api.opaline.dev/v1/packs" \
  -H "Authorization: Bearer opl_live_…" \
  -H "Content-Type: application/json" \
  --data @my-pack.json

Move an alias (e.g. stable) once you're happy:

bash
curl -X PUT "https://api.opaline.dev/v1/packs/acme.bill.validity/aliases/stable" \
  -H "Authorization: Bearer opl_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "version": "1.2.0" }'

Next