# Integration guide
## Issuer side
The issuer owns the Ed25519 private key and decides the claims it is willing to assert.
```rust
use chrono::{Duration, Utc};
use delegated::DelegationTokenBuilder;
use ed25519_dalek::SigningKey;
let signing_key = SigningKey::from_bytes(&[7; 32]); // load from secure key custody in production
let now = Utc::now();
let token = DelegationTokenBuilder::new()
.token_id("cap-01")
.issuer("https://issuer.example")
.agent_id("agent:scheduler")
.delegator_id("user:alice")
.audience("calendar-api")
.allowed_action("calendar.create")
.allowed_resource("calendar:alice")
.issued_at(now)
.expires_at(now + Duration::minutes(10))
.nonce("random-value-at-least-16-bytes")
.key_id("issuer-2026-01")
.build_and_sign(&signing_key)?;
# Ok::<(), Box<dyn std::error::Error>>(())
```
Use a cryptographically random nonce and unique token identifier. Keep lifetimes short. The
builder validates structure, but it cannot assess whether identifiers or authority are correct.
## Resource-server side
Pin or otherwise resolve issuer keys from trusted configuration. Never copy a key from request
JSON into the resolver.
```rust
use delegated::{Evaluator, InMemoryTrustState, OperationContext, PinnedIssuerKeys};
use ed25519_dalek::VerifyingKey;
fn authorize(
request_bytes: &[u8],
issuer_key: VerifyingKey,
) -> Result<bool, Box<dyn std::error::Error>> {
let keys = PinnedIssuerKeys::new();
keys.insert("https://issuer.example", "issuer-2026-01", issuer_key)?;
let state = InMemoryTrustState::new();
// Values come from the operation dispatcher and validated target—not request claims.
let operation = OperationContext::new("calendar-api", "calendar.create")
.with_resource("calendar:alice");
let (decision, audit) =
Evaluator::new(&keys, &state).evaluate(request_bytes, &operation, chrono::Utc::now());
persist_before_execution(audit)?;
Ok(decision.allowed)
}
# fn persist_before_execution(_: delegated::AuditEvent) -> std::io::Result<()> { Ok(()) }
```
Reuse durable issuer-key and trust-state implementations across requests. Constructing a fresh
`InMemoryTrustState` per request disables cross-request replay protection.
## Operation mapping rules
- Define a stable, case-sensitive action vocabulary such as `calendar.create`.
- Derive the action from the handler selected by the server.
- Derive the resource after parsing and validating the actual target.
- Use a stable audience identifier for the service/trust domain.
- Provide delegation depth only if trusted infrastructure tracks it.
- Execute exactly the operation represented by `OperationContext` after authorization.
If a capability contains `allowed_resources`, omission of the host resource denies. If it contains
`max_delegation_depth`, omission of host depth denies. An unconstrained capability intentionally
authorizes any resource for an allowed action and audience.
## Durable integrations
Production deployments normally implement:
- `IssuerKeyResolver` over immutable configuration or a verified key registry.
- `TrustStateStore` over a shared transactional/key-value system.
- `AuditSink` over an append-only, access-controlled log pipeline.
Backend errors fail closed. Set dependency timeouts so failure is prompt and observable.
## Redis trust state (`delegated-redis`)
For multi-instance verifiers, use the [`delegated-redis`](https://crates.io/crates/delegated-redis)
crate instead of `InMemoryTrustState`. It implements the core
[`TrustStateStore`](https://docs.rs/delegated/latest/delegated/trait.TrustStateStore.html)
and [`TrustStateAdmin`](https://docs.rs/delegated/latest/delegated/trait.TrustStateAdmin.html)
traits against Redis.
```rust
use delegated::{Evaluator, OperationContext, PinnedIssuerKeys, VecAuditSink};
use delegated_redis::RedisTrustState;
use ed25519_dalek::VerifyingKey;
fn authorize(
request_bytes: &[u8],
issuer_key: VerifyingKey,
redis_url: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
let keys = PinnedIssuerKeys::new();
keys.insert("https://issuer.example", "issuer-2026-01", issuer_key)?;
// Reuse one Redis-backed store per process (or per pool worker).
let state = RedisTrustState::with_prefix(redis_url, "calendar-api")?;
let operation = OperationContext::new("calendar-api", "calendar.create")
.with_resource("calendar:alice");
let sink = VecAuditSink::new();
let decision = Evaluator::new(&keys, &state).evaluate_and_audit(
request_bytes,
&operation,
chrono::Utc::now(),
&sink,
)?;
Ok(decision.allowed)
}
```
Operational requirements:
- Point every evaluator at the same Redis logical database.
- Use a unique key prefix per environment or service when sharing a cluster.
- Treat Redis outages as authorization failure; do not fall back to in-memory state.
- Run revocation and deny cleanup according to the
[delegated-redis operations guide](../crates/delegated-redis/OPERATIONS.md).
A working end-to-end example lives in
[`examples/reference-stack/`](../examples/reference-stack/README.md).