/v1 Management API. It is built with the Terraform Plugin Framework and speaks plugin protocol 6.
This page is the guide: how to install it, how it authenticates, and what it does with your secrets. Per-attribute reference documentation is generated from the provider schema and ships with the provider.
Requirements
- Terraform >= 1.11 or OpenTofu >= 1.11 for any resource carrying a write-only attribute — which is every resource that accepts secret material. The two ecosystems shipped write-only attributes in different years; 1.11 is the floor in both.
- Terraform >= 1.10 for the ephemeral resource.
- A KnoxCall tenant, and an API key or OAuth client for it.
Install
Because nothing is published, Terraform cannot fetch the provider. Build it and point the CLI at the binary:dev_overrides block to the CLI configuration file — ~/.terraformrc on Linux and macOS, %APPDATA%\terraform.rc on Windows:
Do not run
terraform init in a configuration that uses an overridden provider. Init tries to resolve knoxcall/knoxcall from the registry, where it does not exist, and fails. Run terraform plan and terraform apply directly; Terraform prints a warning that overrides are in effect and skips the dependency lock file. If a stale .terraform.lock.hcl names this provider, delete it.dev_overrides loads whatever binary is on disk at plan time, with no version check, so rebuild after every change.Authenticate
The 90% path is an OAuth client, and it is the default:api_key (or $KNOXCALL_API_KEY) — a tenant API key (tk_…) or a pre-minted OAuth token (kc_…). It conflicts with client_id/client_secret.
Even with api_key, the default auth_mode = "auto" still authenticates with OAuth: the key is used as client credentials (its tk_… prefix is the client id, the whole key is the secret) to mint a short-lived access token, and only falls back once — with a warning — to sending the raw key as a bearer token if the token endpoint answers invalid_client.
tenant_slug deliberately has no environment-variable fallback. An assertion whose expected value comes from the same environment as the credential asserts nothing.
Sandbox
Live and Test are separate data spaces with separate credentials. One provider block manages one(tenant, data space) pair, so managing both means two provider blocks:
sandbox = true selects the Test base URL. An explicit base_url wins over the flag. The credential must belong to the data space it is pointed at — a mismatch is a server-side 403 wrong_key_type, not a silent cross-space write.
Secrets and Terraform state
What the provider will not write to state
There is no plainvalue attribute on any secret resource — not a sensitive one, not an optional one, not a deprecated one. Secret material you supply is supplied through write-only attributes: Terraform reads them from your configuration, the provider sends them to the API, and the framework nullifies them before they can reach a plan file, a state file, or the raw terraform.tfstate bytes. The /v1 API never returns a stored secret value, so there is nothing to read back on refresh either.
Each write-only attribute is paired with a persisted _wo_version integer. Terraform can never see a write-only value, so that integer is the entire mechanism by which a rotation is expressible:
internal/provider/state_safety_gate_test.go) iterates every registered write-only attribute and, for each, applies a real configuration carrying a unique sentinel value, then requires that sentinel to appear nowhere in the parsed state, nowhere in the plan at either pre-apply or post-refresh, and nowhere in the raw bytes on disk — while separately proving the value did reach the API, because a provider that silently dropped every write-only value would otherwise score perfectly. A host-side scanner then checks the server’s own idempotency and audit stores for the same sentinel. The gate is exercised on Terraform 1.11.4 — the floor, which also proves the write-only cases skip rather than error on an older CLI — and on the current release.
What is in state, and why
Four attributes hold credentials the platform mints and returns exactly once. They areComputed and Sensitive — redacted in CLI output, written to state in cleartext:
This is a limit of the plugin framework, not a choice:
Computed and WriteOnly are mutually exclusive, so no schema both captures a value the server generates and refuses to persist it. Rather than leave it implicit, each of the four is registered in internal/provider/wo_guarded_attributes.go with a declared classification and the plan item that would remove it, and a guard fails the build if an attribute carrying secret material is neither write-only nor listed there — or if a listed exception outlives the attribute it excused.
The recommended webhook configuration is therefore the one with no signing secret in it at all:
Two blind spots you need to know about
Both are properties of write-only attributes in general rather than of this provider, and both will bite somebody who has not read them. Two consequences worth stating once: the value your configuration declares is intent,value_version is reality, and the tenant audit log is history — every rotation Terraform performs is recorded server-side as a rotate event, which is where the change history lives given a plan cannot show it. And a secret’s value is held per environment server-side, but the provider models only the base environment’s value today; there is no resource for a per-environment value override.
Resources
Every resource supports create, read, delete andterraform import. All but two also update in place: KnoxCall has no PATCH for route actions or API keys, so every argument of knoxcall_route_action and knoxcall_api_key forces a replacement rather than an in-place edit. Import IDs are frozen ahead of first publish, because an import ID is a public interface.
The
write-only: prefix seeds the _wo_version companion to 1, so a configuration that already declares the pair at version 1 gives an empty first plan rather than proposing to rewrite the credential:
value_wo; it is not re-sent until value_wo_version changes. For the same reason an imported knoxcall_webhook has a null secret_key, and an imported knoxcall_client_credential a null private_key_pem.
Data sources — knoxcall_account, knoxcall_client, knoxcall_environment, knoxcall_environments, knoxcall_route, knoxcall_secret, knoxcall_secrets, knoxcall_webhook_event_types. None of them can return secret material, and a test drives every one against a stub returning credential-shaped fields and then searches the state artifact to prove it.
Ephemeral resources — knoxcall_oauth2_access_token hands the live upstream access token behind a knoxcall_secret_oauth2 to the consuming resource for the duration of one operation, and never writes it to state. KnoxCall owns the refresh, so the upstream refresh token never leaves the platform.
plan when every argument is known, so a plan mints a real token; referencing an unknown attribute — as above, the id of a resource created in the same run — defers the fetch to apply. Do not route the token through an output: outputs are stored in state, and Terraform refuses an ephemeral value there unless the output is itself declared ephemeral = true.
Behavior
The provider’s API client follows the SDK parity standard where applicable:- Retries: 408/429/500/502/503/504 retried up to 3 attempts with half-jitter exponential backoff (100ms base, 5s cap). 409 is never retried. On 429 a server
Retry-Afteris honored, capped at 30s. - Idempotency: every mutating request carries a ULID
X-Idempotency-Keygenerated once per logical request — stable across retries, so transport failures are also safe to retry. - 401: access tokens are minted via the
client_credentialsgrant and refreshed proactively before expiry; an unexpected 401 on a resource request purges the cached token and re-mints once before failing with a clear diagnostic. Underauth_mode = "bearer"there is nothing to re-mint, so a 401 fails fast. - Secret hygiene:
api_keyandclient_secretareSensitivein the schema and never appear in errors, logs, or diagnostics (client_idis a public identifier and stays legible so a rejected client is identifiable). - Cancellation: all requests and retry backoffs respect Terraform’s operation context — interrupts abort promptly.
/v1 response envelope:
Where to go next
- Provision with Terraform — the first route, environment and secret as ~40 lines of HCL.
- Local build instructions, the full resource inventory and the release configuration:
sdk/knoxcall-terraform/README.mdin the monorepo.