Guide · EACH 1 · about 17 minutes
Your first governed agent, by API
One key, six calls, and a record at the end that an auditor would accept. You will register an agent, draw its lines, watch it refused outside them, and stop it. Nothing here needs your identity provider; that comes later, if you want it.
You need an EACH 1 account with a plan started (the Team trial counts) and a confirmed work email. Two steps are for a person in the dashboard, and each says so.
00 · 2 min
Get a key
In the dashboard, open People, then the API keys tab, and create a key. It is shown once. Keep it in your shell, not in a file you commit.
export ENIYAN_API_KEY=et_...Paste it once and every snippet on this page fills in. It stays in this tab's memory: it is never stored, never logged, and this page sends it nowhere.
Check that it works. This read answers with your account's default mode:
curl "https://api.eniyantrust.com/v1/verifications/scope-policy" \
-H "Authorization: Bearer $ENIYAN_API_KEY"What you should see
{
"default_scope_enforcement_mode": "advisory",
"model_reverification_enforced": false
}Advisory is the default: a call outside an agent's scopes is flagged, not refused. Station 02 changes that for your agent.
01 · 4 min
Register your first agent
One call names the agent, says what it is for, declares what it may touch (crm:read and tickets:write) and ties it to the person who answers for it.
operator_credential_id is your own operator credential. A colleague approves you on the Operators tab of People (an account's first two members approve each other). That is the whole human step, and no identity provider is involved. Once approved, your dashboard session reads the id as credential_id from GET /v1/me/operator-verification; put it in OPERATOR_CREDENTIAL_ID.
curl -X POST "https://api.eniyantrust.com/v1/verifications/sessions" \
-H "Authorization: Bearer $ENIYAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "ai_agent",
"external_user_id": "support-agent",
"required_level": "basic",
"agent": {
"purpose": "Tier 1 support: read tickets and draft replies for a person to send.",
"autonomy_level": "human_on_loop",
"access_mode": "read_write",
"environment": "production",
"data_classification": "confidential",
"verification_product": "trust_layer",
"operator_credential_id": "'"$OPERATOR_CREDENTIAL_ID"'",
"delegated_scopes": [
"crm:read",
"tickets:write"
]
}
}'What you should see
{
"id": "6e3ce212-25d3-43e7-8d6d-c675a83de8a7",
"status": "created",
"entity_type": "ai_agent",
"external_user_id": "support-agent",
"operator_credential_id": "f8ed470d-b48f-4e5d-bdb5-8bb968abeb7e",
"delegated_scopes": [
"crm:read",
"tickets:write"
],
"agent_identity_id": "0d62eab4-6286-4038-85aa-8cdeedcfaf94",
"credential_id": null,
"credential_token": null
}The 9 fields that matter here, of the 32 in the answer. No credential yet. Put id in SESSION_ID.
An agent has no hosted identity check to pass, so you complete its registration yourself. The path says simulate because the same route stands in for the hosted check when a person's session is tested. For an agent it is the real completion in every environment.
curl -X POST "https://api.eniyantrust.com/v1/verifications/sessions/$SESSION_ID/simulate-success" \
-H "Authorization: Bearer $ENIYAN_API_KEY"What you should see
{
"id": "6e3ce212-25d3-43e7-8d6d-c675a83de8a7",
"status": "verified",
"completed_at": "2026-10-07T15:17:43.631932Z",
"agent_identity_id": "0d62eab4-6286-4038-85aa-8cdeedcfaf94",
"credential_id": "e564f54b-1116-4b01-b0aa-e68abbecadb7",
"credential_token": "eyJhbGciOiJFUzI1NiIs\u2026"
}The 6 fields that matter here, of the 32 in the answer. Put credential_id in CREDENTIAL_ID and credential_token in AGENT_TOKEN. The token is returned this once; it is cut short here.
If your account reviews agents enrolled with an API key, the completion call still returns the credential. The agent can do nothing until an admin admits it under Agents, on the Agent Fleet page.
02 · 3 min
Draw the lines
The lines are already declared: the two scopes in station 01. What is not decided yet is what happens outside them. Ask for crm:write now, under the default mode:
curl -X POST "https://api.eniyantrust.com/v1/credentials/verify" \
-H "Authorization: Bearer $ENIYAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential_token": "'"$AGENT_TOKEN"'",
"requested_scope": "crm:write"
}'What you should see
{
"valid": true,
"reason": null,
"scope_authorized": false,
"scope_enforcement": "advisory"
}The 4 fields that matter here, of the 25 in the answer. Flagged, and it still passes. Under advisory the violation is recorded and the call is valid.
Now switch this agent to block. This is a dashboard step for an org admin: open Agents, then Agent Fleet, then support-agent, and set Scope Enforcement to Block. The API refuses a key here on purpose: an agent holding the key must not be able to loosen its own lines. This is what a key gets:
What you should see
{
"detail": {
"code": "ADMIN_REQUIRED",
"message": "scope-enforcement policy writes require an org admin (dashboard) session"
}
}03 · 3 min
Make a call. See a refusal.
Run the same call again. This time it fails at the call, not in a report afterwards.
curl -X POST "https://api.eniyantrust.com/v1/credentials/verify" \
-H "Authorization: Bearer $ENIYAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential_token": "'"$AGENT_TOKEN"'",
"requested_scope": "crm:write"
}'What you should see
{
"valid": false,
"reason": "scope_not_authorized",
"scope_authorized": false,
"scope_enforcement": "block"
}The 4 fields that matter here, of the 25 in the answer.
Then ask for something inside them, crm:read:
curl -X POST "https://api.eniyantrust.com/v1/credentials/verify" \
-H "Authorization: Bearer $ENIYAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential_token": "'"$AGENT_TOKEN"'",
"requested_scope": "crm:read"
}'What you should see
{
"valid": true,
"reason": null,
"trust_level": "strong",
"expires_at": "2031-10-06T15:17:43.654896Z",
"scope_authorized": true,
"scope_enforcement": "block"
}The 6 fields that matter here, of the 25 in the answer. Verification is checked on the server every time, so the answer is always the current one.
04 · 3 min
Wire the webhook
Point the events at an endpoint you own (https, a public host) and the record arrives at your door as it happens. Change the URL to yours.
curl -X POST "https://api.eniyantrust.com/v1/webhooks/subscriptions" \
-H "Authorization: Bearer $ENIYAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/eniyan/webhooks",
"events": [
"credential.issued",
"agent.scope_violation",
"credential.revoked"
]
}'What you should see
{
"id": "e97fb8f6-93eb-4d0b-a170-65395e50c53c",
"url": "https://example.com/eniyan/webhooks",
"events": [
"credential.issued",
"credential.revoked",
"agent.scope_violation"
],
"enabled": true,
"secret": "9b6929ed\u2026"
}The 5 fields that matter here, of the 9 in the answer. The signing secret is returned this once; it is cut short here. Each delivery carries X-EniyanTrust-Signature, the HMAC-SHA256 of the raw body under that secret.
Run the refused call from station 03 once more. Your endpoint receives this request:
What you should see
X-EniyanTrust-Signature: sha256=b2652c62…
X-EniyanTrust-Event: agent.scope_violation
X-EniyanTrust-Delivery: fbfc74c6-10c0-4bf1-ad3f-490483b1c7b5
{
"event": "agent.scope_violation",
"id": "fbfc74c6-10c0-4bf1-ad3f-490483b1c7b5",
"timestamp": "2026-10-07T15:17:44.368224+00:00",
"org_id": "ddd610af-ab86-4997-9e06-1c6ee2145406",
"data": {
"agent_id": "34043ff4-43da-4fc8-9e69-3a39e40e7d20",
"requested_scope": "crm:write",
"enforcement": "block",
"blocked": true,
"delegated_scopes": [
"crm:read",
"tickets:write"
]
}
}credential.issued arrives the next time a credential is issued, and credential.revoked in the next station.
05 · 2 min
Stop it
Revoke the credential, then run the verify call again. It fails at once, signature or not.
curl -X POST "https://api.eniyantrust.com/v1/credentials/$CREDENTIAL_ID/revoke" \
-H "Authorization: Bearer $ENIYAN_API_KEY"What you should see
{
"id": "e564f54b-1116-4b01-b0aa-e68abbecadb7",
"external_user_id": "support-agent",
"revoked": true,
"revoked_at": "2026-10-07T15:17:44.392168Z"
}The 4 fields that matter here, of the 36 in the answer.
curl -X POST "https://api.eniyantrust.com/v1/credentials/verify" \
-H "Authorization: Bearer $ENIYAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential_token": "'"$AGENT_TOKEN"'",
"requested_scope": "crm:read"
}'What you should see
{
"valid": false,
"revoked": true,
"reason": "Credential has been revoked"
}The 3 fields that matter here, of the 25 in the answer.
And your endpoint receives:
What you should see
X-EniyanTrust-Signature: sha256=64c9dc76…
X-EniyanTrust-Event: credential.revoked
X-EniyanTrust-Delivery: d8b93423-f2c3-473b-99dd-353de4489de6
{
"event": "credential.revoked",
"id": "d8b93423-f2c3-473b-99dd-353de4489de6",
"timestamp": "2026-10-07T15:17:44.396841+00:00",
"org_id": "ddd610af-ab86-4997-9e06-1c6ee2145406",
"data": {
"credential_id": "e564f54b-1116-4b01-b0aa-e68abbecadb7",
"external_user_id": "support-agent",
"entity_type": "ai_agent",
"revocation_reason": null,
"revoked_at": "2026-10-07T15:17:44.392168+00:00"
}
}Revoking is permanent for this credential. To run the agent again, reissue its credential from its page in the dashboard; the declared scopes carry over.
To pause an agent without revoking anything, an org admin can stop it in the dashboard: Stop support-agent on the agent's page, or Stop in the bar at the top for every agent. A stopped agent is refused at its next check, which can be up to a minute later, and Release brings it back exactly as it was. Stopping is a dashboard action: the API refuses a key here too.
06 · optional
Later, when you want it
Everything above ran with one engineer and one key. When the fleet grows, the rest plugs in.
- Enrol a fleet with the SDK: governed runs, with the file, web and mail gates.
- Bring your people in: sign-in and offboarding from your identity provider, when you want it.
- Consumer delegation: agents acting for your customers, with step-up and five-minute tokens.
- API reference: every route used here, with its errors.