Signing Groups
Require multiple parties to approve before an action is signed. Configure a required number of participants for your signing workflow. Available on the Enterprise plan.
When to use it
Use a signer group when an integration requires several participants to authorize signing. For example, a deployment workflow can require two of three configured signers. Ordinary action receipts do not require a signer group. For a person’s review of an existing receipt, use Approvals.
In Settings → Enterprise → Multi-party signing, Signing requests shows the collected and required signatures, status, and expiry. Open a request to inspect its action and participating signers. Signer groups shows the configured threshold and whether each group is active.
| Dashboard | Integration / API |
|---|---|
| Monitor requests and inspect collected signatures | Create a request and submit each participant’s authorization |
| Inspect existing signer groups | Create groups, add participants, and change the required count |
| Use Developer tools for existing key and risk administration | Automate key lifecycle, delegation, and risk configuration |
| Investigate a pending or expired request | Check status, handle expiry, and decide whether the external action may proceed |
The dashboard monitors signing requests; it does not currently provide a button to sign as a participant. Participant authorization uses the API and its identity checks. The separate Approvals page cannot substitute for a signing-group participant.
Developer tools retains keys, risk profiles, delegations, and audit queries for teams already using them. Start with the setup sequence below before using these controls; generating a keypair alone does not connect an agent or configure its signers.
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
The hosted service manages encrypted key shares and combines selected shares during signing. A participant threshold does not by itself establish independent custody by separate organizations.
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.
Signing requests
For a request that collects participant signatures, use signing sessions. An integration requests an action, and entities authorize it individually until the required count is met. These sessions are separate from the human evidence-review queue at /approvals/.
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. Your integration must check the result and enforce the decision before proceeding; a completed session does not execute the action.
The session authorization endpoints and /keypairs/{keypair_id}/sign are separate operations. A completed session collects participant signatures; the group-sign endpoint combines selected key shares to sign supplied bytes. Do not assume one automatically invokes or gates the other.
Session States
| Status | Description |
|---|---|
pending |
Waiting for approvals |
approved |
Required approvals met, action approved |
expired |
24h elapsed without reaching required approvals |
The dashboard loads up to 100 signing requests. Its search and date filters apply to those loaded records. Use the API when your integration needs programmatic access to request history.
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"]
}
)
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 |
The first matching rule wins. Give a specific exception a lower priority number than a broader fallback; otherwise the fallback matches first.
Participant counts
Signing sessions count distinct participant signatures. Although risk profiles accept entity_weights, the session approval endpoint does not use those weights to decide whether the required count is met. Do not rely on one participant counting as several approvals.
Time-dependent approvals
Approval requirements can be adjusted based on time of day. Operations outside business hours can require additional approvals:
{
"time_schedule": {
"after_hours": {
"start": "17:00",
"end": "08:59",
"approvals_add": 1
}
}
}
This adds 1 during the matching 17:00–08:59 UTC window, bounded by the number of configured participants. Schedule keys name periods; each period supplies start, end, and approvals_add. The classifier uses UTC and applies the first matching period.
Framework Integration
SDK adapters can record framework activity. Multi-party authorization still needs an explicit integration: create a signing request, collect the required participant signatures, check its status, and enforce the result before executing the protected action. The human Approvals API is a separate workflow.
LangChain
AsqavCallbackHandler (from asqav.extras.langchain) records supported chain, tool, and LLM events. Attaching the callback does not implement the signing-group authorization flow for your application.
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) evaluates a tool request with a preflight check and records permitted calls. It does not itself collect signing-group participant authorizations. Wire the group workflow into your application when you require it.
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 human review of existing evidence.
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
- Implement your policy check - A Class C label identifies a policy participant; your integration must perform the actual check
- 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