Signing Groups
Require multiple parties to approve before an action is signed. Multi-party signing splits cryptographic authority so no single entity can act alone. Available on the Enterprise plan.
How It Works
Multi-party signing uses a t-of-n model: you define n entities (agents, humans, or policies), and any t of them must approve before a signature is produced.
The resulting signature is a standard ML-DSA-65 signature - verifiers cannot tell it was produced by multiple parties. This is not multi-sig. It is a single cryptographic signature from distributed key shares.
- Configure - Create a signing group for an agent with a minimum approval count
- Add entities - Register the parties who can approve (agents, humans, or policies)
- Generate keypair - Secret key is split into Shamir shares, one per entity
- Sign - Provide t entity IDs and a message. Shares are combined to produce the signature
Entity Classes
Five types of entities can participate in multi-party signing:
| Class | Type | Description | Role |
|---|---|---|---|
A |
Agent Signer | Automated AI agent | Autonomous signing participant |
B |
Human Operator | Human approver (requires user_id) |
Manual approval gate |
C |
Policy Engine | Automated policy engine | Rule-based automated gatekeeper |
D |
Compliance Verifier | Automated regulatory compliance check | Validates action meets regulatory requirements |
E |
Organizational Authority | Senior organizational override | Executive-level approval for high-risk operations |
If any Class C (policy) entity exists in the config, at least one policy entity must be included in every signing request.
Setup
1. Create a Signing Group
Attach a signing group to an existing agent:
POST /api/v1/signing-groups/configs
{
"agent_id": "agent_abc123",
"min_approvals": 2
}
Response:
{
"config_id": "cfg_xyz789",
"agent_id": "agent_abc123",
"min_approvals": 2,
"total_entities": 0,
"is_active": true
}
2. Add Entities
Register the parties who can approve actions:
# Add an AI agent entity
POST /api/v1/signing-groups/configs/{config_id}/entities
{
"name": "payment-agent",
"entity_class": "A"
}
# Add a human approver
POST /api/v1/signing-groups/configs/{config_id}/entities
{
"name": "finance-lead",
"entity_class": "B",
"user_id": "usr_jane"
}
# Add a policy engine
POST /api/v1/signing-groups/configs/{config_id}/entities
{
"name": "spend-limit-policy",
"entity_class": "C"
}
Each entity gets its own ML-DSA keypair. The algorithm defaults to ml-dsa-65 and can be set to ml-dsa-44 or ml-dsa-87.
3. Generate Signing Group Keypair
Generate a shared keypair. The secret key is split into Shamir shares - one per entity, encrypted at rest. The raw shares are never exposed.
POST /api/v1/signing-groups/keypairs
{
"config_id": "cfg_xyz789"
}
Response:
{
"keypair_id": "kp_abc456",
"public_key_b64": "base64...",
"algorithm": "ML-DSA-65",
"min_approvals": 2,
"total_shares": 3,
"share_assignments": [
{"entity_id": "ent_...", "entity_name": "payment-agent", "participant_index": 1},
{"entity_id": "ent_...", "entity_name": "finance-lead", "participant_index": 2},
{"entity_id": "ent_...", "entity_name": "spend-limit-policy", "participant_index": 3}
]
}
Signing
Produce a multi-party signature by specifying which entities participate:
POST /api/v1/signing-groups/keypairs/{keypair_id}/sign
{
"message_b64": "base64-encoded-message",
"participant_entity_ids": ["ent_abc", "ent_def"]
}
Response:
{
"signature_b64": "base64...",
"public_key_b64": "base64...",
"algorithm": "ML-DSA-65",
"participants": [...]
}
The output is a standard 3,309-byte ML-DSA-65 signature. Any ML-DSA-65 verifier can validate it using the public key - no special signing-group-aware verification needed.
Approval Sessions
For approvals-queue workflows, use approval sessions. An agent requests an action, and entities approve individually until the required approval count is met.
Request an Action
POST /api/v1/signing-groups/sessions
{
"agent_id": "agent_abc123",
"action_type": "payment:transfer",
"params": {
"amount": 50000,
"currency": "USD"
}
}
Response:
{
"session_id": "thr_abc789",
"status": "pending",
"approvals_required": 2,
"signatures_collected": 0,
"expires_at": "2025-01-16T10:00:00Z"
}
Approve
POST /api/v1/signing-groups/sessions/{session_id}/approve
{
"entity_id": "ent_finance_lead"
}
Response:
{
"session_id": "thr_abc789",
"signatures_collected": 2,
"approvals_required": 2,
"status": "approved",
"approved": true
}
Sessions expire after 24 hours if the required approval count is not met. Each entity can only approve once per session.
Session States
| Status | Description |
|---|---|
pending |
Waiting for approvals |
approved |
Required approvals met, action approved |
expired |
24h elapsed without reaching required approvals |
Managing Configs
Update Min Approvals
PUT /api/v1/signing-groups/configs/{config_id}
{
"min_approvals": 3
}
Remove an Entity
DELETE /api/v1/signing-groups/entities/{entity_id}
List Entities
GET /api/v1/signing-groups/configs/{config_id}/entities
Response:
{
"entities": [
{
"entity_id": "ent_...",
"name": "payment-agent",
"entity_class": "A",
"algorithm": "ML-DSA-65",
"public_key_b64": "base64...",
"is_active": true
}
],
"count": 3
}
Key Lifecycle
Key lifecycle operations let you rotate key material or recover lost shares without disrupting the rest of the signing group.
Key Share Refresh
Refresh generates new Shamir shares for an existing keypair without changing the public key. Use this to rotate key material on a schedule or after a security event. At least t contributing entity shares are required. All active delegations are automatically invalidated when a refresh completes.
import httpx
response = httpx.post(
"https://api.asqav.com/api/v1/signing-groups/keypairs/kp_abc/refresh",
headers={"X-API-Key": "sk_live_..."},
json={"contributing_entity_ids": ["ent_1", "ent_2"]}
)
Share Recovery
When an entity loses its key share, other entities can reconstruct it without ever exposing the master key. Exactly t contributing entities must participate. The recovered share is re-encrypted and stored for the target entity.
response = httpx.post(
"https://api.asqav.com/api/v1/signing-groups/keypairs/kp_abc/recover",
headers={"X-API-Key": "sk_live_..."},
json={
"target_entity_id": "ent_lost",
"contributing_entity_ids": ["ent_1", "ent_2"]
}
)
Anomaly Detection
Multi-party signing sessions are monitored for anomalous activity. Anomaly events are logged and can trigger webhook notifications via the session.anomaly event.
Delegation
Entities can temporarily delegate their signing authority to another entity. Useful for planned absences, handoffs, or on-call rotations. Delegation certificates are ML-DSA signed and time-bounded - they expire automatically and cannot be renewed without creating a new delegation.
# Create a 24-hour delegation
response = httpx.post(
"https://api.asqav.com/api/v1/signing-groups/delegations",
headers={"X-API-Key": "sk_live_..."},
json={
"config_id": "cfg_xyz",
"delegator_entity_id": "ent_alice",
"delegate_entity_id": "ent_bob",
"duration_hours": 24
}
)
While the delegation is active, ent_bob can contribute in place of ent_alice. The delegator retains their share and can still participate directly. Delegations are revoked automatically when a key refresh occurs.
Risk Profiles
Risk profiles dynamically raise or lower the required approval count based on the action being requested. This lets you enforce stricter controls for sensitive operations without managing separate signing groups.
Action patterns use glob matching (fnmatch), so payment:* matches payment:transfer, payment:refund, and so on. Rules are evaluated in priority order - the lowest priority number wins.
# High-risk payments require all 3 signers
httpx.post(
"https://api.asqav.com/api/v1/risk-rules",
headers={"X-API-Key": "sk_live_..."},
json={
"name": "high-risk-payments",
"action_pattern": "payment:*",
"risk_level": "high",
"approval_override": 3,
"priority": 10
}
)
| Risk Level | Typical Use |
|---|---|
low |
Read-only or reversible actions |
medium |
State changes with limited blast radius |
high |
Financial transactions, data deletion |
critical |
Infrastructure changes, key operations |
Rules are evaluated lowest priority number first. Set priority: 1 for rules that must always apply, and higher numbers for more specific overrides.
Weighted Approvals
Risk profiles can assign different weights to entity classes. For example, a compliance verifier (Class D) approval may count as 2 shares toward the approval count, while a standard agent (Class A) counts as 1:
{
"entity_weights": {"A": 1.0, "B": 1.5, "C": 1.0, "D": 2.0, "E": 2.0}
}
Time-Dependent Approvals
Approval requirements can be adjusted based on time of day. Operations outside business hours can require additional approvals:
{
"time_schedule": {
"start_hour": 9,
"end_hour": 17,
"timezone": "UTC",
"approvals_add": 1
}
}
This adds 1 to the required approval count for operations outside the 09:00-17:00 window.
Framework Integration
The SDK extras ship signing adapters for LangChain and CrewAI that emit a signed event for every tool, chain, and LLM call. Pair them with a signing group plus the Claude Code hook (or the Approvals API) to require multi-party approval before high-risk tool calls execute.
LangChain
AsqavCallbackHandler (from asqav.extras.langchain) signs every chain, tool, and LLM event. Attach it to your chain as a callback, then route tool calls through the MCP server's gate_action tool when they need signing group approval.
from asqav.extras.langchain import AsqavCallbackHandler
handler = AsqavCallbackHandler(agent_name="my-langchain-agent")
chain.invoke(input, config={"callbacks": [handler]})
CrewAI
AsqavGuardrailProvider (from asqav.extras.crewai) runs a preflight check on each tool call and signs the verdict. When the configured policy points at a signing group, approvals are held until the group's required count is met.
from asqav.extras.crewai import AsqavGuardrailProvider
provider = AsqavGuardrailProvider(agent_name="my-crew")
crew = Crew(agents=[...], tasks=[...], guardrail_provider=provider)
See the Integrations page for every adapter and its repository, and the Approvals page for how to wire signing group decisions into the gate.
Use Cases
- Financial controls - Require human + policy approval for high-value transactions
- Multi-agent pipelines - Multiple agents must agree before executing critical actions
- Regulatory compliance - Enforce separation of duties with mandatory policy entities
- Approvals queue - Combine AI autonomy with human oversight for sensitive operations
Best Practices
- Include a policy entity - Automated guardrails that must always approve
- Set min_approvals >= 2 - Single-party approval defeats the purpose
- Use approval sessions for humans - Async workflow with 24h expiry
- Use direct signing for automated flows - When all participants are agents/policies