Dual Control
Two-person approval on high-risk model actions and policy writes.
Dual control requires a second authenticated operator before a high-risk mutation takes effect. The first actor proposes. The second actor, with a disjoint role, approves or rejects. The model does not self-approve. Dashboard sessions and API keys are both subject to the gate.
Gated Actions
| Action | Default on Enterprise |
|---|---|
| Publish a prompt template | Required when evals_required is true |
| Change project residency | Required |
| Raise a budget above the desk threshold | Required |
Expand allowed_models on a routing policy | Required |
| Disable PII redaction on a project | Required |
Disable fail_closed residency | Required |
Create a production API key with governance:manage | Required |
| Disable dual control itself | Required, plus owner role |
risk.report.approve | Required on Eridian Risk |
risk.assessment.approve | Required |
risk.case.close (high severity) | Required |
risk.case.hold.lift | Required |
legal.redline.confirm when playbook high_risk | Required |
Archive a Dev repository when evals_required | Required |
Starter and Growth may enable the same gates. They are off by default on those plans. Risk-specific approve/reject flows: Risk Approvals.
Propose
POST /v1/approvals
Authorization: Bearer eridian_sk_...
Content-Type: application/json
{
"project_id": "prj_legal_001",
"action": "template.publish",
"resource": "tpl_contract_summary_v3",
"payload": { "version": "3.3.0" },
"justification": "Eval run evr_19c4 passed pii_leak=0 and exact_match=0.84."
}
{
"id": "apr_44e1",
"object": "eridian.approval",
"status": "pending",
"action": "template.publish",
"proposer_id": "usr_kadart",
"expires_at": 1718486400
}
The proposer cannot approve their own request. Approver must hold governance:manage and must not be the same SSO principal.
Approve or Reject
POST /v1/approvals/apr_44e1/approve
POST /v1/approvals/apr_44e1/reject
Authorization: Bearer eridian_sk_...
{
"comment": "Checked eval report evr_19c4 and residency eu-west-1."
}
Approved mutations apply immediately and emit approval.approved plus the underlying audit event (template.publish, policy.updated, and so on). Rejected mutations emit approval.rejected and leave the resource unchanged.
Timeout
Pending approvals expire after 24 hours (configurable 1 to 72). Expiry emits approval.expired. The resource stays on its last published version.
Inference Path
Dual control does not sit on ordinary POST /v1/inference. It sits on control-plane writes and on optional tool actions you mark as high_risk in a template (for example, a workflow that emits an outbound payment instruction). Those template-backed calls return HTTP 403 dual_control_required with an approval_id until the second operator approves.
{
"error": {
"type": "eridian.error",
"code": "dual_control_required",
"message": "Action template.publish requires a second approver.",
"request_id": "axm_req_8f3a2b1c",
"param": "apr_44e1"
}
}
See Evaluations, Audit Log, Risk Approvals, and Governance.
Production API credentials are issued with an institution workspace. Contact sales if you need access.