Skip to main content
[AVAILABLE]. Every endpoint, error, and worked example below was run against the live deployment while writing this page, not written from the spec alone. Base URL: https://parmana-api-real.vercel.app. Repo copy of this guide: LIVE-API-GUIDE.md (repo root), this page and that file are kept in sync.

Goal

Build a demo by calling a real, running Parmana deployment directly, no local setup, no mocked responses. This is the real, production @parmana/api code, not a sandbox or a simplified illustration.

What this deployment is, and isn’t

Is: a real authorization engine. Submit a Business Transaction, it evaluates the named policy against your declared signals, produces a real, deterministic, Ed25519-signed decision (approve/reject), and persists it durably. Rejections complete end to end, signed, durable, retrievable. Genuinely useful for demoing policy authoring, signal binding, guardrails correctly declining a request, cryptographic proof of a decision, independent offline verification, and audit trails. Isn’t: a live payment/CRM/deployment system. No connector is registered on this deployment, no HubSpot token, no GitHub App credentials configured, and it isn’t running in test mode either. This is deliberate: it mirrors a real, documented finding in this codebase (see What Parmana does not claim and the vendor-payment policy’s own history) that a capability should not be wired to a connector until its signals are independently verified, not merely caller-declared. Concretely: an approved decision reaches Policy Engine and gets signed, then fails with a 500 (No connector registered for capability '<name>') at the dispatch stage, for every capability, including the ones with real connector code (HubSpot, GitHub), since their credentials aren’t configured on this deployment. A denied decision never reaches that stage (policy rejection happens before dispatch), so it always completes cleanly. Plan demos around that: “the system correctly declines” is a complete, real demo on this deployment; “money actually moves” is not, unless you add real connector credentials.

Authentication

Bearer token in the Authorization header. Two keys are currently provisioned: Raw key values are shown once in a terminal and never committed to the repo, only their salted hashes live in the deployment’s environment configuration. To mint your own:
This prints the raw key once and a config entry to add to the deployment’s caller-key list, then requires a redeploy to take effect. See Authentication for the full caller-auth model, and LIVE-API-GUIDE.md (repo root) for the exact commands. Principal scoping is a separate check from capability scoping: a key with no explicit principal grant may only assert authority.principalId equal to its own caller id. Simplest path, set authority.principalId to "demo" in every transaction you submit with the demo key.

The shape of a request

Hard requirements, all fail-closed with a clear 400/403 if wrong:
  • businessTransactionId must be a real UUID, not a slug.
  • authority.authorityType must be one of USER, ROLE, SERVICE, ORGANIZATION. An autonomous agent maps to SERVICE ("AGENT" is not a valid value, a real mistake made and caught while building this deployment).
  • policy.name/policy.version must match a real, deployed policy, the caller names the policy explicitly, it is not inferred from intent.action.
  • Every fact a policy’s rules reference must appear in signals. A fact declared as boundSignals is additionally cross-checked against the real intent field it’s bound to, or the whole request is rejected before Policy Engine ever runs.

Available policies

Full signal schemas for every policy: LIVE-API-GUIDE.md (repo root).

Writing a new policy

A policy is one JSON file: policies/<policyId>/<version>/policy.json. Four condition shapes (leaf comparison, all, any, always), a fixed operator set (eq, neq, gt, gte, lt, lte, between, in, not_in, contains, not_contains, contains_all, contains_any, starts_with, ends_with, matches, exists, not_exists, is_true, is_false, is_null, is_not_null, length_eq, length_gt, length_gte, length_lt, length_lte, type_is), and one rule that’s easy to miss: every fact your rules reference must appear in either boundSignals or unboundSignalReasons, or the policy fails to load at all, no silent, unacknowledged gaps allowed. The expense-reimbursement@1.0.0 policy above is a real, worked example of this, full schema, the exact validation rule that would reject a badly-formed policy, and how to deploy a new one (git add + vercel deploy --prod, since policies are baked into the build) are all in LIVE-API-GUIDE.md (repo root), tested live while writing it: write, validate, deploy, call, get a signed decision, independently verify.

Endpoints

Full table, every route, and copy-pasteable curl examples: LIVE-API-GUIDE.md (repo root). Machine-readable spec: GET /openapi.yaml or the REST API reference.

Verifying a result independently

GET /trust-records/:id only returns a record for a capability with a registered connector, none on this deployment. For a real, fetchable, signed artifact today, use the Refusal Record: fetch it via GET /refusal/:id, then verify with zero further server calls using verifyExecutionTrustRecordOffline from @parmana/crypto for an ExecutionTrustRecord, or POST /refusal/verify (server-side, but still genuinely cryptographic and unauthenticated) for a RefusalRecord. See Verify a trust record independently for the fully offline path, and examples/tutorials/107-offline-verification/ through 110-hybrid-signature-downgrade-protection/ for runnable, tested demonstrations of every scenario, including a hybrid (Ed25519 + ML-DSA-65) record and a downgrade-attack proof.

Next

Write your first policy

A slower, more conceptual walkthrough of policy authoring than the reference above.

Verify a trust record independently

The fully offline verification path, in depth.