Appearance
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:
| Prefix | Kind | Use |
|---|---|---|
opl_admin_ | master | Your tenant master key. Mint and revoke your own data-plane keys via /v1/keys. Issued at onboarding; scoped to your tenant. |
opl_live_ | data | Runtime operations on the live namespace (metered). |
opl_test_ | data | Runtime 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.jsonMove 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
- Pack authoring: write, lint, publish, and simulate packs.
- Trail & verification: how each decision is recorded and proven.
- TypeScript SDK: a fully typed client generated from the contract.
- MCP setup: drive the engine from an AI agent.
- API reference: the full interactive reference.