Enruta

Docs · Objects

Consent record and KYA attestation

LiveUpdated

Agents. Written for people who build agents, the hosts they run in, and the people and companies the agents pay for.

Every “consent” field in the industry is a live API attribute, and every KYA framework answers “who is the agent” but not “what may it do”. The consent record is the mandate’s parent: a principal’s one-time or standing authorization of one agent, signed and exportable. The KYA attestation records who the agent is: Enruta reads and seals someone else’s identity claim or a verifiable anchor, and never runs a registry of its own.

FieldContents
idcns_01M2F0Q3W7T1K5N9B4D8H2XR
principal{org_id, principal_id_readable, display_name, verified_by}verified_by: oauth · passkey · email_link · sso
agent{agent_id, kya_attestation_id, operator_org}
scope{max_per_payment, max_cumulative, currency, payees[] or categories[], valid_from, valid_until, recurrence}Five things a card-on-file consent lacks: an amount cap, single use, an expiry, a purpose and an offline-verifiable signature
merchant_policies[]{merchant, policy_type, policy_url, policy_hash, consented_at, acceptance_method}policy_type: refund · cancellation · advance_payment · guaranteed_reservation · estimated_authorization · repeated_transaction, one per Visa Core Rules 4.1.24.6, 4.1.24.8 and 4.1.24.9 clause
acknowledgements[]{text_hash, accepted_at, method}The hash of the “I am responsible for what this agent does” text the person accepted
retention{minimum_days, until, export_on_expiry}540 days for card rails, 730 for ACH
revocation{revoked_at, by, effect, institution_notified_at, effective_at, cascades_scheduled, notified_institution_at, cascade}effect: stops_new_mandates_only; revocation is not a reversal. effective_at is when it takes effect: at once, or a later time before which new mandates still work. cascades_scheduled says whether what was already scheduled stops too, cascade what that did, and notified_institution_at when a rail was first asked, through an interface it has, to cancel the voided mandates’ credentials (null when none has one). institution_notified_at is when a consumer told their institution, which decides whether later debits count as unauthorized. A revocation made before these fields existed reads as effective at revoked_at, with no cascade
signatureThe principal’s side (a passkey assertion, or Enruta signing on the strength of an approval event) plus Enruta’s ES256
state, source, source_connection_idactive · revoked · expired; console · oauth · api
record_id, revoke_record_id, valid_from, valid_untilThe sealed linked records of the grant and of the latest revocation (kind: consent_record, chain: consent_for, org-signed → customer_attested)

A consent stays active until valid_until passes (the finalize job marks it expired) or a revocation takes effect (revoking a consent). Under a policy with require_consent, a request from an agent without an active consent is denied with consent_revoked; a policy without require_consent records consents without enforcing them. A recurrence can name the consent that authorizes it.

{
  "id": "cns_01M2F0Q3W7T1K5N9B4D8H2XR", "state": "active", "source": "console",
  "principal": { "org_id": "org_halvern", "principal_id_readable": "halvern/dana.reyes@halvernrobotics.com", "display_name": "Dana Reyes", "verified_by": "passkey" },
  "agent": { "agent_id": "agt_procurement_02", "kya_attestation_id": "kya_01M2F1R6W2T9K4Q7N3B8D5H1XS", "operator_org": "org_halvern" },
  "scope": { "max_per_payment": "200.00", "max_cumulative": "1000.00", "currency": "USD", "categories": ["office_supplies"], "valid_from": "2026-09-13", "valid_until": "2027-03-31" },
  "merchant_policies": [
    { "merchant": "reamandrule.com", "policy_type": "refund", "policy_url": "https://reamandrule.com/policies#returns", "policy_hash": "sha256:…", "consented_at": "2026-09-13T09:00:00Z", "acceptance_method": "console" },
    { "merchant": "reamandrule.com", "policy_type": "cancellation", "policy_url": "https://reamandrule.com/policies#returns", "policy_hash": "sha256:…", "consented_at": "2026-09-13T09:00:00Z", "acceptance_method": "console" }
  ],
  "acknowledgements": [{ "text_hash": "sha256:…", "accepted_at": "2026-09-13T09:00:00Z", "method": "passkey" }],
  "retention": { "minimum_days": 540, "until": "2028-09-22", "export_on_expiry": true },
  "revocation": null,
  "record_id": "rec_01M2F0QF3T8W2K5Q9N4B7D1H6XR",
  "revoke_record_id": null
}

Where consents come from today: the console (“Grant consent” on an agent), the OAuth consent page (connecting Claude.ai or ChatGPT to an organization and an agent creates one; existing connections were back-filled), and POST /v0/consents.

A developer key calls POST /v0/consents/{id}/revoke, or a person uses “Revoke” in the console. A revocation has a time boundary: it takes effect at effective_at, at once by default or at a later time. A later one is scheduled: the consent stays active, new mandates still work until then, and the scheduled revocation is sealed as its own record (subject.action: "schedule_revocation") chained consent_for to the grant. Asking again for a later time does not move it; a revocation effective now brings it forward. From effective_at the consent no longer covers the agent, even before the finalize job applies the revocation (its apply_revocations step). Taking effect seals the revocation (subject.action: "revoke", with its time boundary and what its cascade did), marks the consent revoked and sends consent.revoked.

Without cascades_scheduled a revocation stops new mandates only: mandates issued before it and not yet expired stay valid. With cascades_scheduled: true what is already scheduled stops too, each change sealed on its own: recurrences made under the consent end; the agent’s issued, unused mandates are voided (their payments seal with outcome voided and reason consent_revoked) and the rail is asked to cancel their single-use credentials; the agent’s payment approvals still pending for requests made while the consent was in force expire, each request ending expired with consent_revoked among its reasons, so it is not retryable, and its budget reservation released; and when no other active consent covers the agent, its other recurrences pause and the agent is paused. A refund waiting for approval is left alone, and so is a request whose decision named another consent. None of it is a reversal: money already paid stays paid.

POST /v0/consents/cns_01M2F0Q3W7T1K5N9B4D8H2XR/revoke
{ "effective_at": "2026-10-01T00:00:00Z", "cascades_scheduled": true }

200 { "id": "cns_01M2F0Q3W7T1K5N9B4D8H2XR", "state": "active", …,
      "revocation": { "revoked_at": "2026-09-14T10:00:00Z", "by": "key_01M2E7…", "effect": "stops_new_mandates_only", "institution_notified_at": null,
                      "effective_at": "2026-10-01T00:00:00Z", "cascades_scheduled": true, "notified_institution_at": null, "cascade": null },
      "record_id": "rec_01M2F0QF3T8W2K5Q9N4B7D1H6XR", "revoke_record_id": "rec_01M3G1…" }

// on October 1 the finalize job applies it; GET /v0/consents/{id} then answers
"state": "revoked",
"revocation": { …, "cascade": { "applied_at": "2026-10-01T00:05:00Z", "mandates_voided": ["mnd_01M3H0…"], "recurrences_ended": ["rcu_01M2J4…"], "recurrences_paused": [],
                                "approvals_expired": ["apr_01M3H2…"], "agent_paused": true, "pause_record_id": "rec_01M3H3…" } }

A revocation effective now answers with the consent already revoked and its cascade. approvals_expired is on cascades applied from this release. An effective_at more than a minute in the past is refused with 400.

KYA attestation

FieldContents
id, agent_idkya_01M2F1R6W2T9K4Q7N3B8D5H1XS
anchor_typeregistry for a registry claim (the tiers below), or one of the six identity anchors: agent_key, code_digest, workload_identity, hardware_attestation, legal_entity, collateral
party, issuer, claimsFor anchors: whose identity (developer, platform, user), who vouched for it, and the subset of claims kept
registry, registry_ref, tierFor registry claims; see the tiers below
method, statusstatus: verified · stale (past the issuer’s validity) · revoked · unverified
evidence_hash, evidence, verified_at, expires_atEnruta stores what it was given and its hash; the bundle carries the evidence so the verifier can re-hash it
attestation_record_idThe sealed linked record (kind: kya_attestation, chain: attestation_for)
TierRegistriesWhat it is
A, verifiable claimcloudflare_web_bot_auth, persona_relay, vouched_kyaA signature or credential Enruta can check
B, network identity anchorvisa_agentic_directory, mastercard_agent_pay, skyfireA reference into a card-network or marketplace directory
C, vendor assertedvendor_attested with vendor and reference_idThe operator says so

The agent’s verified anchors add up to an assurance level from A0 to A3, carried on every payment record as agent.identity and in the risk assessment as agent_identity, so a merchant sees unverified when there is nothing and a policy can step up or deny on it (identity requirements). The Accept middleware can derive a merchant’s bot-management fields from the same object.

Pausing an agent

An owner pauses an agent in the console, or a developer key calls POST /v0/agents/{id}/pause with a reason. From that moment every request by the agent is denied with agent_paused before any policy is read, and the pause itself is a linked record (kind: agent_pause, chain: pause_of, subject { action: pause | resume, by, reason, at }, org-signed). POST /v0/agents/{id}/resume seals the resume the same way; the webhook is agent.paused.

Interfaces

SurfaceCall
RESTPOST /v0/consents, GET /v0/consents, GET /v0/consents/{id}, POST /v0/consents/{id}/revoke (effective_at?, cascades_scheduled?, institution_notified_at?); POST /v0/attestations/nonce, POST /v0/attestations, GET /v0/attestations, GET /v0/agents/{id}/identity; POST /v0/agents/{id}/pause, POST /v0/agents/{id}/resume
SDKconsents.create/list/get, consents.revoke(id, { effective_at, cascades_scheduled }) (Python consents.revoke(consent_id, effective_at=None, cascades_scheduled=None)), attestations.nonce/create/list, agents.identity(id), agents.pause(id, reason), agents.resume(id)
CLIenruta consents list | revoke <id>, enruta attest workload | key, enruta agents pause | resume <agt> [--reason]
ConsoleIdentity, anchors, consents and attestations per agent under /agents; “Pause” and “Resume” with a reason; “Revoke” on a consent, effective now or at a date (UTC), optionally also ending scheduled payments and unused mandates; all consents under /settings
Webhooksconsent.revoked when a revocation takes effect, with effective_at, cascades_scheduled, notified_institution_at and cascade; approval.completed for each approval a cascade expired; agent.paused
ExportPOST /v0/exports with consents: true includes the sealed consent records (Compliance export)