Production base URL:
https://parmana-api-real.vercel.app. Checked against
the code and the deployment on 2026-10-02.Goal
Call the real, running production deployment directly, no local setup, no mocked responses. It runs the same@parmana/api code as the sandbox, with real connectors and real
credentials, so it is open only to callers its operator has issued a key.
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.paytm:refund is a real, wired connector on this deployment:
PAYTM_CONNECTOR_URL/PAYTM_CONNECTOR_SHARED_SECRET point at a real, separately deployed
parmana-paytm-agent service, which itself calls Paytm’s staging API. An approved
paytm:refund request reaches Paytm’s real staging API and returns a real result, with a
full cross-service audit trail correlated by businessTransactionId. Full runbook:
End to end: agent to Parmana to Paytm.
Isn’t (HubSpot, GitHub, Slack, as last checked by the operator on 2026-09-30): no HubSpot
token, GitHub App credentials or Slack bot token is configured, and it isn’t running in test
mode either. An approved request for one of these answers 503 CONNECTOR_NOT_REGISTERED, which
is how to check it today. This mirrors a real, documented
finding in this codebase (see Limitations
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 request for hubspot:deal-update, github:pr-merge, slack:post-message
and the other HubSpot and GitHub actions is refused with a 503
(code CONNECTOR_NOT_REGISTERED) before release, and nothing runs, since their
credentials aren’t configured on this deployment. A capability registered as an external
connector is released to its own endpoint. A denied decision never reaches that
stage (policy rejection happens before dispatch), so it always completes cleanly regardless
of capability. Plan demos around that: “the system correctly declines” is a complete, real
demo for any capability; “money/data actually moves” is real and demonstrable specifically
for paytm:refund as of this correction, not yet for HubSpot/GitHub without adding real
credentials.
Authentication
Bearer token in theAuthorization header. Production has no public or demo key: every
key was rebuilt from scratch on 2026-09-27 and again on 2026-09-30, and the operator issues one
per caller, scoped to the capabilities it needs (for example paytm-refund-agent, allowed only
paytm:refund). To try requests without a key of your own, use the sandbox.
Raw key values are shown once in a terminal and never committed to the repo; only their
hashes live in the deployment’s environment configuration. To mint a key for an agent, scoped to
its capabilities:
"*" to an agent. See
Authentication for the full caller auth model, and
Credentials: maker, checker and approver 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 your own caller id.
The shape of a request
400/403 if wrong:
- Every request needs a signed human approval in
signals.approvalArtifact, for this action and resource (and amount, where the policy names one), with the policy’s approval signal settrue.managerApproved: truewithout it is refused. See Human approval. businessTransactionIdmust be a real UUID, not a slug.authority.authorityTypemust be one ofUSER,ROLE,SERVICE,ORGANIZATION. An autonomous agent maps toSERVICE("AGENT"is not a valid value, a real mistake made and caught while building this deployment).policy.name/policy.versionmust match a real, deployed policy, the caller names the policy explicitly, it is not inferred fromintent.action.- Every fact a policy’s rules reference must appear in
signals. A fact declared asboundSignalsis additionally cross-checked against the realintentfield it’s bound to, or the whole request is rejected before Policy Engine ever runs.
Available policies
No AI agent action is authorized without a signed human approval, reads included. Every current policy needs one; the column on the right names the signal and what the approval must cover. Older versions that approved without a person (such asvendor-payment@2.0.0 or
github-pr-read@1.0.0) stay in the repository as history and are refused when they load. See
Human approval.
Full signal schemas for every policy: each policy file under
policies/<name>/<version>/policy.json (signalsSchema).
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.1.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 a new one
goes live (proposed and approved through policy governance, no deploy) are in
Write your first policy and
Policy lifecycle and approvals.
Endpoints
Machine-readable spec:
GET /openapi.yaml or the REST API
reference.
Verifying a result independently
An executed action has a Trust Record (GET /trust-records/:id); a refused one has a
Refusal Record (GET /refusal/:id). Verify a Trust Record with zero further server calls
using verifyExecutionTrustRecordOffline (TypeScript) or verify_execution_trust_record_offline
(Python) and the key from GET /keys/default, and a Refusal Record with POST /refusal/verify
(server side, but cryptographic and unauthenticated). 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.
Connect an external agent
How an AI agent authenticates, sends a governed request, and handles
APPROVED/REJECTED, with the real parmana-phinite-agent integration as the
worked example.