How configuration is loaded
- The server reads the process environment. It also loads a
.envfile, found by walking up from the package directory, but a variable already set in the process environment always wins over the file. - In production, set variables in your platform (Vercel, Fly.io, your container runtime) and do not ship a
.envfile. - Values are read once at startup. Change a variable, then restart or redeploy.
- A wrong value fails at startup with a message that names the variable. It does not fail later under traffic.
NODE_ENV is anything other than test, which includes production. “If” means it is required only under the
stated condition. “No” means optional.
The minimum for a production start
These are the variables you must set for a production server that uses local signing keys and Supabase storage. Everything else has a safe default.| Variable | Example value |
|---|---|
NODE_ENV | production |
PARMANA_POLICY_DIR | ./policies |
PARMANA_API_KEYS | [{"callerId":"my-agent","keyHash":"<64 hex characters>","allowedCapabilities":["paytm:refund"]}] |
PARMANA_STORAGE | supabase |
DATABASE_URL | postgresql://postgres.<project>:<password>@<host>:6543/postgres |
PARMANA_KEY_DIR | ./keys (with default.private.pem, default.public.pem, gateway.private.pem and gateway.public.pem inside), or use PARMANA_KEY_MATERIAL_JSON |
Core
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
NODE_ENV | Set it explicitly | development | production, development, test, or any string | Selects behavior. test turns off durable stores, key checks and connector requirements, and must never be used in production. development and test relax the SIGNING_READINESS_CHECK and EXECUTION_INTENTS_CHECK gates. Governance is relaxed only with PARMANA_ALLOW_UNGOVERNED_POLICY=true in those two environments, see POLICY_EXECUTION_VERIFICATION_ENFORCED. Any other value, or leaving it unset, keeps all three gates on. |
PORT | No | 3000 | Integer | TCP port the API listens on. |
HOST | No | 0.0.0.0 | Host name or IP address | Interface the API binds to. |
SHUTDOWN_TIMEOUT_MS | No | 10000 | Milliseconds | On SIGTERM or SIGINT the server stops accepting connections and drains in flight requests. If they have not finished within this time it exits anyway. |
LOG_LEVEL | No | info | debug, info, warn, error | Minimum level written to the structured logs. An unrecognized value falls back to info. |
Caller authentication
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_API_KEYS | Always, unless PARMANA_AUTH_DISABLED is true | Empty | JSON array, see below | The callers allowed to use the API. The server refuses to start if it is empty or unset. |
PARMANA_AUTH_DISABLED | No | Off | Only the exact string true turns it on | Accepts every request with no caller authentication. For local development only. Never set it in a real deployment. GET /ready reports authDisabled so monitoring can alert on it. |
The format of PARMANA_API_KEYS
A JSON array. Each entry is an object:
| Field | Required | Meaning |
|---|---|---|
callerId | Yes | A non empty string that names the caller. |
keyHash | Yes | The SHA 256 hash of the raw key, as 64 lowercase hexadecimal characters. Only the hash is ever stored. |
allowedCapabilities | No | The actions (intent.action) this key may invoke, for example ["paytm:refund"]. If unset or empty the key may invoke nothing. The single value "*" allows every capability. |
allowedPrincipalIds | No | The authority.principalId values this key may assert. If unset the key may only assert its own callerId. |
allowedTenantIds | No | The metadata.tenantId values this key may send. The tenant id picks the signing key tenant.<tenantId>, so it must come from the key. If unset or empty, a request with a tenantId is refused with 403 TENANT_NOT_ALLOWED; a request without one is unaffected. |
credentialHolderType | No | USER, ROLE, SERVICE or ORGANIZATION. Only USER keys may use the policy governance endpoints. If unset the key is treated as not human. |
stepUpPublicKey | No | A PEM encoded Ed25519 public key (SPKI). Required for a USER key that approves or rejects policy changes. |
npm run generate:api-key -- --caller-id my-agent --allowed-capabilities paytm:refund
PARMANA_API_KEYS. Give the raw key to the caller
and never store it anywhere else. To make a human key add --credential-holder-type USER, and add
--generate-step-up-key to also generate the step up signing key.
If the value is not valid JSON, is not an array, or an entry is malformed, the server stops at startup with a message
that names PARMANA_API_KEYS.
Policies
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_POLICY_DIR | Always | None | A directory path | The directory that holds <name>/<version>/policy.json files. A relative path resolves from the process working directory, so start the server from the directory that contains it. |
POLICY_EXECUTION_VERIFICATION_ENFORCED | No | Enforced | true only has an effect, and only in test and development | Requires every policy to have a signed approval record whose hash matches the live policy before it can authorize or release anything. In production, or with NODE_ENV unset or any value other than test or development, it is always on and this variable is ignored. |
PARMANA_ALLOW_UNGOVERNED_POLICY | No | Off | true only has an effect in test and development | Skips the governance approval record check for policies. Ignored in every other NODE_ENV, and ignored when POLICY_EXECUTION_VERIFICATION_ENFORCED=true. The policy content hash is still required. Never set it in a deployment. |
PARMANA_SKIP_DOTENV | No | Off | 1 only | Stops the loaders from reading a .env file. The test suite sets it, so a developer’s local .env cannot change test results. |
PARMANA_VERIFIER_KEY_ID | No | signal-verifier | Any key id that exists in PARMANA_KEY_DIR or KMS | The key the signal verifier signs its read-only HubSpot fetches with. The gateway accepts this key only for hubspot:deal-fetch, so a leaked verifier key cannot release a write. The key must be provisioned before a HubSpot deal update can verify its signals. |
POLICY_GOVERNANCE_INTEGRITY_CHECK_INTERVAL_MS | No | 300000 (5 minutes) | Milliseconds | How often the server re checks that live policy files still match their approval records. A value of zero or less turns the periodic check off and leaves only the startup check. A value that is not a number uses the default. |
Storage
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_STORAGE | No | memory | memory, postgres, supabase | Where business transactions and trust records are kept. memory loses everything on restart and must not be used in production. postgres uses any Postgres through DATABASE_URL, and is the name for a self hosted database. supabase is the same implementation under its older name, kept so existing deployments are unchanged. |
DATABASE_URL | Not test | None | A Postgres connection string | A direct Postgres connection string. It is required whenever NODE_ENV is not test, regardless of PARMANA_STORAGE, because the nonce store, the caller audit trail, the execution audit trail, the approval nonce store and the step up nonce store are always durable outside tests. It also backs shared rate limit counters. Use the Supabase transaction pooler string for serverless hosts, see Production deployment. |
PARMANA_DB_SCHEMA | No | Unset (the public schema) | A plain Postgres name, such as parmana_sandbox | Puts this process’s tables on another schema of the same database, so two systems can share one database. Connections set search_path to it. Use the session pooler (port 5432) with it: a transaction pooler may not keep the setting. |
DATABASE_PROVIDER | Never set it | None | None | Removed. If it is set at all, the server stops with “DATABASE_PROVIDER is no longer read; set PARMANA_STORAGE instead.” |
Signing keys and gateway identity
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_KEY_DIR | Not test | None | A directory path | The directory holding key files named <keyId>.private.pem and <keyId>.public.pem. The server needs default (signs trust records, receipts and authorizations) and the gateway key (gateway unless you changed PARMANA_GATEWAY_KEY_ID). With KEY_PROVIDER=aws-kms the default key comes from KMS instead, but the gateway key is still read from this directory. |
PARMANA_KEY_MATERIAL_JSON | No | None | {"<keyId>":{"privateKeyPem":"...","publicKeyPem":"..."}} | Writes key files into PARMANA_KEY_DIR at startup for any key file that does not already exist. Use it on platforms where you cannot mount files. An invalid value stops the server. |
KEY_PROVIDER | No | local | local, aws-kms | Where the signing key lives. local reads PEM files. aws-kms signs inside AWS KMS so the key is never released. The values azure-key-vault, gcp-kms and hsm have no implementation, so the server refuses to start with them. |
AWS_REGION | If KEY_PROVIDER is aws-kms or PARMANA_SECRETS_PROVIDER is aws-secrets-manager | None | An AWS region such as ap-south-1 | The AWS region for KMS and Secrets Manager calls. |
AWS_ROLE_ARN | If KEY_PROVIDER is aws-kms or PARMANA_SECRETS_PROVIDER is aws-secrets-manager | None | An IAM role ARN | The role the server assumes to call AWS, using the Vercel OIDC token. It never uses a static access key. |
PARMANA_VERIFICATION_KEY_ID | No | default | Key id | The key id that signs and verifies Execution Trust Records. |
PARMANA_VERIFICATION_SECONDARY_KEY_ID | No | default-secondary | Key id | The key id for the second signature when CRYPTO_MODE is hybrid. |
PARMANA_GATEWAY_ID | No | parmana-gateway | Letters, digits, ., _ and - | The identity the gateway presents to connectors. An invalid value stops the server. |
PARMANA_GATEWAY_KEY_ID | No | gateway | Letters, digits, ., _ and - | The key id of the gateway attestation key file in PARMANA_KEY_DIR. An invalid value stops the server. |
INBOUND_AUDIENCE | No | Off | Any non-empty string | The audience a partner’s inbound refund decision must name. When set, POST /v1/inbound/decisions is mounted, and the server refuses to start unless PARMANA_KEY_MATERIAL_JSON holds a private key for PARMANA_GATEWAY_KEY_ID. RFC-0025. |
SIGNING_READINESS_CHECK | No | Enforced | true only has an effect, and only in test and development | Before each action is released, proves that the evidence signing path works, and refuses with 503 SIGNING_UNAVAILABLE if it does not. In production it is always on and this variable is ignored. Under KMS the probe uses kms:Sign and kms:GetPublicKey at most once a minute per instance. |
EXECUTION_INTENTS_CHECK | No | Enforced | true only has an effect, and only in test and development | Before each action is released, signs and stores an Execution Intent, and refuses with 503 EXECUTION_INTENT_UNAVAILABLE, releasing nothing, if it cannot. In production it is always on and this variable is ignored. It needs the execution_intents table, so apply migration 20260921120000 before deploying. Under KMS it costs one more kms:Sign per released action. See Execution Intents. |
npx tsx scripts/generate-keypair.ts --algorithm ed25519 --key-id default
npm run generate:gateway-keys
PARMANA_KEY_DIR, or ./keys if it is unset, and refuse to overwrite existing files unless you add
--force. Keep the private files secret, and never commit them.
Cryptography
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PRIMARY_SIGNATURE_PROVIDER | No | ed25519 | ed25519, dilithium3 (alias ml-dsa-65) | The signature algorithm. Only these two are implemented. The values ecdsa-p256, dilithium5 and sphincs-plus have no implementation, so the server refuses to start with them. dilithium3 needs Node 24 or later and produces randomized signatures. |
SECONDARY_SIGNATURE_PROVIDER | If CRYPTO_MODE is hybrid | None | Same values as above | The second algorithm for hybrid signing. |
CRYPTO_MODE | No | single | single, hybrid | hybrid adds a second signature from SECONDARY_SIGNATURE_PROVIDER. pq is refused at startup, because no code reads it. hybrid needs KEY_PROVIDER=local and both key pairs (default and default-secondary) in PARMANA_KEY_DIR; the server refuses to start otherwise, and refuses hybrid with KEY_PROVIDER=aws-kms. |
HASH_PROVIDER | No | sha256 | sha256 | The hash algorithm. The values sha3-512 and blake3 have no implementation, so the server refuses to start with them. |
HYBRID_SIGNATURE_REQUIRED | No | Off | Only the exact string true turns it on | When on, a record without a valid hybrid signature is rejected instead of passing on its classical signature alone. |
HYBRID_SCHEMA_VERSION | No | 2 | 2, 3 | The schema version that hybrid signatures are made under. 2 signs the canonical fields plus schemaVersion. 3 signs every top-level field except the signature envelope, verifications and receipts. Records made under 3 are not readable by a verifier that implements only version 2, so switch only after the verifiers you rely on support it. 3 is refused unless HYBRID_SCHEMA_V3_ACKNOWLEDGED=true is also set. Any other value stops the server from starting. |
POLICY_CHANGE_CHECKERS | No | Unset (no one may approve) | A JSON object of policy name to an array of caller ids, for example {"vendor-payment":["checker-1"]} | Which callers may approve a change to each policy. A caller not listed for a policy gets 403 CHECKER_NOT_IN_SCOPE. Unset lists no one, so policy changes cannot be approved until this is set. |
KILL_SWITCH_ENABLED | No | Unset (switch off) | true or false | Turns on the operator kill switch. Off means no kill switch routes or guards are mounted. On needs a database (DATABASE_URL) and KILL_SWITCH_RESET_NEEDS_SECOND_OPERATOR. Any other value stops startup. |
KILL_SWITCH_RESET_NEEDS_SECOND_OPERATOR | Yes, when KILL_SWITCH_ENABLED=true | None | true or false | true in production: a reset takes two different operators. false only for test or non-production use. Nothing defaults it. |
HYBRID_REQUIRED_FROM | No | Unset (no cutover) | An ISO-8601 UTC timestamp ending in Z | The cutover. A record created at or after this instant must carry a valid hybrid envelope, or it fails verification. Set it only once every record created from that instant on is hybrid-signed (CRYPTO_MODE=hybrid). Otherwise the server rejects its own new records. Use the same instant in the offline verifier’s trust file. With no cutover, the offline verifier refuses a record with no hybrid envelope unless its trust file sets allowLegacyOnly to true. |
HYBRID_SCHEMA_V3_ACKNOWLEDGED | No | Unset | Only the exact string true counts | Required with HYBRID_SCHEMA_VERSION=3. Set it only after the verifiers and SDKs you rely on read version 3. |
Authorization and receipts
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
EXECUTION_AUTHORIZATION_TTL_SECONDS | No | 120 | Positive number of seconds | How long a signed execution authorization stays valid. The gateway rejects any authorization whose lifetime is more than 300 seconds, so do not set this above 300. |
TRUST_PROFILE | No | v1 | v1 | The trust profile. v1 is the only value. |
RECEIPT_VERSION | No | 1 | A string | The version stamped on receipts. |
Rate limits
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
RATE_LIMIT_EXECUTE_PER_MINUTE | No | 30 | Integer | Requests per minute per authenticated caller on POST /execute. Over the limit the API returns 429 with a Retry-After header. |
RATE_LIMIT_HEALTH_PER_MINUTE | No | 300 | Integer | Requests per minute per IP address on GET /health and GET /ready. |
RATE_LIMIT_PUBLIC_PER_MINUTE | No | 60 | Integer | Requests per minute per IP address, shared across POST /refusal/verify, POST /execution-intents/verify, POST /audit/verify and /handbook, the unauthenticated routes that check signatures or write data, and key discovery (GET /keys/:keyId, GET /.well-known/jwks.json). |
RATE_LIMIT_AUTH_FAILURE_PER_MINUTE | No | 30 | Integer | Failed caller authentications (401) per minute per IP address, on every authenticated route. Each one writes a signed audit event, so this stops a stream of bad keys from using up signing capacity. Requests with a valid key do not count. Over the limit, every request from that address gets 429 until the minute resets. |
DATABASE_URL set, the counters are shared across instances. Without it they are per process, so the effective limit
is the value multiplied by the number of instances, and the server logs rate_limit_store_not_durable.
Approval notifications
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
APPROVAL_WEBHOOK_URL | With the next one | None: nothing is sent | An https URL (http only in test or development) | Where to POST an approval.needed event when a refused request would have been authorized with a signed approval. See Human approval. |
APPROVAL_EMAIL_TO | For email notifications | None: no email | Email addresses, separated by commas | Who is emailed each approval.needed event, through Resend. Needs RESEND_API_KEY and a sender, or the server stops at startup. |
APPROVAL_EMAIL_FROM | No | Parmana approvals <approvals@RESEND_EMAIL_DOMAIN> | A sender, such as Ops <ops@notify.example.com> | The sender. Its domain must be verified in Resend. |
RESEND_API_KEY | With APPROVAL_EMAIL_TO | None | Set by the Resend integration | The Resend API key. |
RESEND_EMAIL_DOMAIN | With APPROVAL_EMAIL_TO, unless APPROVAL_EMAIL_FROM is set | None | Set by the Resend integration | The verified sending domain. |
APPROVAL_WEBHOOK_SECRET | With APPROVAL_WEBHOOK_URL | None | A random string, or a secret reference | Signs each event (HMAC SHA256). See PARMANA_SECRETS_PROVIDER. Setting only one of the two stops the server at startup. |
Browser access
Production sets none of these except, if it wants a browser console,PARMANA_CORS_ORIGINS.
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_CORS_ORIGINS | No | None: no CORS header | Origins separated by commas, each exactly https://host[:port], no path or slash | Lets a browser on these origins call the API. Only a listed origin is named in Access-Control-Allow-Origin, never *; a preflight from any other origin gets 403. The key is still required on every request that needs one. |
PARMANA_SANDBOX | No | Off | true, false or unset | Sandbox mode: mounts POST /sandbox/approvals, the demo approver. Refused at startup when caller authentication is disabled or any connector variable below (Paytm, HubSpot, Slack, GitHub, including PARMANA_GITHUB_VERCEL_CONNECT_CONNECTOR_ID) is defined, even empty, so a sandbox can never reach a real system. Never set it in production. |
PARMANA_SANDBOX_APPROVER_ID | With PARMANA_SANDBOX=true | None | The approver id, such as sandbox-demo-approver | The demo approver. Its public key must be added in the sandbox through an approver change, like any approver. Set without PARMANA_SANDBOX=true, the server refuses to start. |
PARMANA_SANDBOX_APPROVER_KEY_ID | With PARMANA_SANDBOX=true | None | The key id, such as sandbox-demo-approver-key-1 | The demo approver’s key id. |
PARMANA_SANDBOX_APPROVER_PRIVATE_KEY | With PARMANA_SANDBOX=true | None | An Ed25519 private key, PEM; one line with literal \n is accepted | Signs the demo approvals. A sandbox key only: it must never be a production approver’s key. |
Local test approver
No agent action is authorized without a signed human approval, so a local test server needs an approver it trusts.| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_TEST_APPROVER_PUBLIC_KEY_FILE | Never in production | None: not trusted | A path to an Ed25519 public key PEM file | Trusts the approver local-test-approver, key local-test-approver-key-1, with this key, for the Quickstart. Honored only when NODE_ENV is test. Set with any other NODE_ENV, the server refuses to start. Real approvers are added through Manage approvers. |
Secrets
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PARMANA_SECRETS_PROVIDER | No | env | env, aws-secrets-manager | How connector credential variables are read. With env, the value of a variable such as HUBSPOT_PRIVATE_APP_TOKEN is the secret. With aws-secrets-manager, the value is the name or ARN of a secret whose SecretString holds the credential, fetched at first use and cached in memory for one hour, never written to disk. |
Connectors
A connector registers only when its credentials are present. A missing connector does not stop the server. It logs an event such asslack_connector_unavailable and requests for that connector’s actions fail.
Paytm refund connector
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
PAYTM_CONNECTOR_URL | Both this and the secret, or neither | None | A URL that starts with https:// | The URL of the separate example-agent service that performs the refund. Outside tests, a URL that does not start with https:// stops the server at startup. |
PAYTM_CONNECTOR_SHARED_SECRET | Both this and the URL, or neither | None | A string | The shared secret Parmana sends to that service. Setting only one of the two stops the server at startup. |
PAYTM_CONNECTOR_TIMEOUT_MS | No | 10000 | Positive number of milliseconds | How long Parmana waits for that service before failing the call. A value that is not a positive number stops the server. |
HubSpot connector
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
HUBSPOT_PRIVATE_APP_TOKEN | For the connector to register | None | A HubSpot private app token, or a secret reference | The credential. See PARMANA_SECRETS_PROVIDER. |
HUBSPOT_BASE_URL | No | The HubSpot API | A URL | Overrides the API base URL, for testing. Must use https://. http:// is accepted only for localhost, and only when NODE_ENV=test. |
HUBSPOT_PRIVATE_APP_TOKEN_ROTATED_AT | No | None | An ISO 8601 date | When the token was last rotated. The server logs a warning when the token is older than 90 days, or when this is set to something that is not a date. |
Slack connector
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
SLACK_BOT_TOKEN | For the connector to register | None | A Slack bot token, or a secret reference | The credential. See PARMANA_SECRETS_PROVIDER. |
SLACK_BASE_URL | No | The Slack API | A URL | Overrides the API base URL, for testing. Must use https://. http:// is accepted only for localhost, and only when NODE_ENV=test. |
SLACK_ALLOWED_CHANNEL_IDS | For any post to succeed | None: every post is refused | Comma separated channel ids, such as C0123456789,C0987654321 | The only channels a message may go to. The caller’s channelAuthorized is not trusted. |
GitHub connector
| Variable | Required | Default | Values or format | What it does |
|---|---|---|---|---|
GITHUB_APP_ID | For the connector to register, with the next two | None | A GitHub App id | The GitHub App used to act on pull requests. |
GITHUB_INSTALLATION_ID | With the other two | None | An installation id | The installation of that App. |
GITHUB_APP_PRIVATE_KEY | With the other two | None | A PEM private key, or a secret reference | The App’s private key. |
GITHUB_BASE_URL | No | The GitHub API | A URL | Overrides the API base URL, for GitHub Enterprise or testing. Must use https://. http:// is accepted only for localhost, and only when NODE_ENV=test. |
PARMANA_GITHUB_VERCEL_CONNECT_CONNECTOR_ID | No | None | A Vercel Connect connector id | An alternative credential source using Vercel Connect. If it is set, it takes precedence and the three variables above are not used. |
Variables that look relevant but the server does not read
| Variable | Where you may see it | Meaning |
|---|---|---|
SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY | .env.example, CI, tutorials | The server does not read these. It talks to Postgres only through DATABASE_URL. They are used by some tests and tutorials. Setting them on the server has no effect. |
TEST_GITHUB_APP_ID, TEST_GITHUB_INSTALLATION_ID, TEST_GITHUB_APP_PRIVATE_KEY, TEST_HUBSPOT_PRIVATE_APP_TOKEN, TEST_SLACK_BOT_TOKEN, TEST_PAYTM_CONNECTOR_SHARED_SECRET | Tests | Read only when NODE_ENV is test. Never set them in production. |
TEST_GITHUB_PULL_NUMBER, TEST_GITHUB_REPOSITORY, TEST_HUBSPOT_DEAL_ID | Tests | Test fixtures for live integration tests. |
ALLOW_LIVE_GITHUB, ALLOW_LIVE_HUBSPOT, ALLOW_LIVE_SUPABASE | Tests | Opt in switches for tests that call real services. Never set them in production. |
Startup errors and what to do
The server stops at startup, before it accepts any request, when a required setting is missing or wrong. This is deliberate: a misconfigured server never runs half configured.| The error says | Cause | Fix |
|---|---|---|
PARMANA_POLICY_DIR is not set | No policy directory | Set PARMANA_POLICY_DIR to the directory that holds your policies. |
No caller authentication keys are configured | PARMANA_API_KEYS is empty or unset | Generate at least one entry and set it. For local development only, set PARMANA_AUTH_DISABLED=true. |
PARMANA_API_KEYS is not valid JSON or must be a JSON array | Malformed value | Fix the JSON. It must be an array of objects with callerId and a 64 character lowercase hex keyHash. |
PARMANA_KEY_DIR is not set | No key directory | Set PARMANA_KEY_DIR. |
Signing key material for keyId "default" is missing | No default.private.pem or default.public.pem in PARMANA_KEY_DIR | Generate the key pair, mount the files, or set PARMANA_KEY_MATERIAL_JSON. |
Durable NonceStore requires DATABASE_URL (or CallerAuditSink, ExecutionAuditSink, ApprovalNonceStore, PolicyChangeStepUpNonceStore) | DATABASE_URL is not set and NODE_ENV is not test | Set DATABASE_URL to a direct Postgres connection string. |
PARMANA_STORAGE=supabase requires DATABASE_URL | Supabase storage without a connection string | Set DATABASE_URL. |
Invalid PARMANA_STORAGE: <value> (or CRYPTO_MODE, HASH_PROVIDER, KEY_PROVIDER, PARMANA_SECRETS_PROVIDER, TRUST_PROFILE, SIGNATURE_PROVIDER) | A value outside the allowed list | Use one of the values in the tables above. |
DATABASE_PROVIDER is no longer read | The removed variable is set | Delete it and set PARMANA_STORAGE. |
<NAME>=<value> is not implemented in this build | azure-key-vault, gcp-kms or hsm | Use local or aws-kms. |
KEY_PROVIDER=aws-kms is configured, but no KMS key named "default" is reachable | KMS key missing, wrong region or role, or no permission | Check AWS_REGION, AWS_ROLE_ARN, the key alias default, and that the role can use the key. |
PAYTM_CONNECTOR_URL and PAYTM_CONNECTOR_SHARED_SECRET must both be set, or both left unset | Only one of the pair is set | Set both, or remove both. |
Invalid PARMANA_GATEWAY_ID | The id has characters other than letters, digits, ., _ and - | Use only those characters. |
PARMANA_KEY_MATERIAL_JSON is not valid JSON | Malformed value | Fix the JSON. It must be an object keyed by key id, each with privateKeyPem and publicKeyPem. |
PARMANA_SANDBOX=true refuses to start while a built in connector is configured | A connector variable is defined on a sandbox, even empty | Delete every Paytm, HubSpot, Slack and GitHub variable from the sandbox, and PARMANA_GITHUB_VERCEL_CONNECT_CONNECTOR_ID. |
PARMANA_SANDBOX=true needs the demo approver or ... refuses to start with PARMANA_AUTH_DISABLED=true | Sandbox mode is incomplete | Set the three PARMANA_SANDBOX_APPROVER_* variables, and keep caller authentication on. |
PARMANA_SANDBOX_APPROVER_... set without PARMANA_SANDBOX=true | A demo approver variable outside a sandbox | Remove it. The demo approver exists only in a sandbox. |
PARMANA_CORS_ORIGINS: "<value>" must be exactly an origin | A path, a trailing slash or * | List bare origins such as https://docs.parmanasystems.com. |
HASH_PROVIDER=<value> is not implemented in this build | HASH_PROVIDER is sha3-512 or blake3 | Use sha256. |