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.

DashboardIntegration / API
Monitor requests and inspect collected signaturesCreate a request and submit each participant’s authorization
Inspect existing signer groupsCreate groups, add participants, and change the required count
Use Developer tools for existing key and risk administrationAutomate key lifecycle, delegation, and risk configuration
Investigate a pending or expired requestCheck 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.

  1. Configure - Create a signing group for an agent with a minimum approval count
  2. Add entities - Register the parties who can approve (agents, humans, or policies)
  3. Generate keypair - Secret key is split into Shamir shares, one per entity
  4. 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
Policy Enforcement

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:

http
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:

http
# 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.

http
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:

http
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

http
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

http
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

http
PUT /api/v1/signing-groups/configs/{config_id}

{
  "min_approvals": 3
}

Remove an Entity

http
DELETE /api/v1/signing-groups/entities/{entity_id}

List Entities

http
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.

python
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.

python
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.

python
# 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.

python
# 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
Priority Order

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:

json
{
  "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.

python
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.

python
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

Best Practices