Skip to main content

ACA-Py Agent Configuration

This guide provides detailed configuration steps for ACA-Py (Aries Cloud Agent - Python) agents used in CCDigital for verifiable credential issuance and proof verification.
ACA-Py implements the Aries protocols for verifiable credential exchange. CCDigital uses two agents: an issuer for credential issuance and a holder for user authentication.

Architecture Overview

CCDigital’s ACA-Py architecture consists of:
  • Issuer Agent: Issues verifiable credentials to citizens based on database records
  • Holder Agent: Holds user credentials and presents proofs during login
  • Admin API: REST API for agent management and credential operations
  • Indy Ledger: Stores schemas, credential definitions, and DIDs

Prerequisites

Before configuring agents, ensure you have:
  • Docker and Docker Compose installed
  • Indy ledger running (see Hyperledger Indy Setup)
  • Registered issuer DID with endorser role
  • Python 3.10+ for issuance scripts
  • Network connectivity between agents

Issuer Agent Setup

1

Create Issuer Wallet Configuration

Define the wallet configuration for the issuer agent:
# docker-compose.yml
issuer-agent:
  image: bcgovimages/aries-cloudagent:py36-1.16-1_0.7.5
  command: >
    start
    --inbound-transport http 0.0.0.0 8000
    --outbound-transport http
    --endpoint http://issuer-agent:8000
    --admin 0.0.0.0 8021
    --admin-insecure-mode
    --wallet-type indy
    --wallet-name issuer_wallet
    --wallet-key issuer_key_12345
    --seed 000000000000000000000000Issuer01
    --genesis-url http://von-network:9000/genesis
    --label "CCDigital Issuer"
    --auto-provision
    --log-level info
  ports:
    - "8000:8000"
    - "8021:8021"
  networks:
    - indy-network
2

Start Issuer Agent

Start the issuer agent container:
docker-compose up -d issuer-agent
3

Verify Issuer Agent Status

Check that the agent is ready:
curl http://localhost:8021/status
Expected response:
{
  "version": "0.7.5",
  "label": "CCDigital Issuer"
}
4

Configure Admin API Access

Set the issuer admin URL in CCDigital:
export INDY_ISSUER_ADMIN_URL="http://localhost:8021"
export ACAPY_VERIFIER_ADMIN_URL="http://localhost:8021"
The --admin-insecure-mode flag is used for development. In production, use --admin-api-key to secure the admin API and set INDY_ADMIN_API_KEY accordingly.

Holder Agent Setup

1

Create Holder Wallet Configuration

Define the wallet configuration for the holder agent:
# docker-compose.yml
holder-agent:
  image: bcgovimages/aries-cloudagent:py36-1.16-1_0.7.5
  command: >
    start
    --inbound-transport http 0.0.0.0 8010
    --outbound-transport http
    --endpoint http://holder-agent:8010
    --admin 0.0.0.0 8031
    --admin-insecure-mode
    --wallet-type indy
    --wallet-name holder_wallet
    --wallet-key holder_key_12345
    --genesis-url http://von-network:9000/genesis
    --label "CCDigital Holder"
    --auto-provision
    --auto-accept-invites
    --auto-accept-requests
    --auto-store-credential
    --auto-respond-credential-offer
    --auto-verify-presentation
    --log-level info
  ports:
    - "8010:8010"
    - "8031:8031"
  networks:
    - indy-network
The --auto-* flags simplify credential acceptance for development. For production, implement manual acceptance flows with user consent.
2

Start Holder Agent

Start the holder agent container:
docker-compose up -d holder-agent
3

Verify Holder Agent Status

Check that the agent is ready:
curl http://localhost:8031/status
4

Configure Admin API Access

Set the holder admin URL in CCDigital:
export INDY_HOLDER_ADMIN_URL="http://localhost:8031"
export ACAPY_HOLDER_ADMIN_URL="http://localhost:8031"

# Configure holder label for connection resolution
export INDY_HOLDER_LABEL="CCDigital Holder"

# Use 'auto' to resolve connection by label
export INDY_HOLDER_CONNECTION_ID="auto"

Wallet Configuration

Wallet Types

ACA-Py supports multiple wallet types:
  • indy: Default Indy wallet (recommended)
  • askar: Newer Aries Askar wallet
CCDigital uses the Indy wallet type by default.

Wallet Security

For production deployments:
  1. Use strong wallet keys (not simple passwords)
  2. Store wallet keys securely (e.g., HashiCorp Vault, AWS Secrets Manager)
  3. Enable wallet encryption at rest
  4. Use PostgreSQL wallet storage for better performance

PostgreSQL Wallet Storage

For production, use PostgreSQL instead of SQLite:
issuer-agent:
  command: >
    start
    --wallet-type indy
    --wallet-name issuer_wallet
    --wallet-key ${WALLET_KEY}
    --wallet-storage-type postgres_storage
    --wallet-storage-config '{"url":"postgres-host:5432"}'
    --wallet-storage-creds '{"account":"postgres","password":"${DB_PASSWORD}"}'
    # ... other flags

Admin API Endpoints

Issuer Agent Endpoints

The issuer agent exposes these key endpoints:
EndpointMethodDescription
/statusGETAgent status and version
/connectionsGETList all connections
/connections/create-invitationPOSTCreate connection invitation
/schemasPOSTCreate credential schema
/credential-definitionsPOSTCreate credential definition
/issue-credential-2.0/sendPOSTIssue credential
/present-proof-2.0/send-requestPOSTRequest proof presentation
/present-proof-2.0/recordsGETList proof exchanges

Holder Agent Endpoints

EndpointMethodDescription
/statusGETAgent status
/connectionsGETList connections
/connections/receive-invitationPOSTAccept invitation
/credentialsGETList stored credentials
/present-proof-2.0/recordsGETList proof exchanges

Admin API Authentication

Secure the admin API with an API key:
command: >
  start
  --admin-api-key ${ADMIN_API_KEY}
  # ... other flags
Configure CCDigital to use the API key:
export INDY_ADMIN_API_KEY="your-secure-api-key-here"
The IndyAdminClient automatically includes the API key in requests via the X-API-Key header.

Connection Establishment

Automatic Connection Resolution

CCDigital can automatically resolve connections by label:
# Enable auto-resolution
export INDY_HOLDER_CONNECTION_ID="auto"
export INDY_HOLDER_LABEL="CCDigital Holder"
The system searches for an active connection where their_label matches the configured label.

Manual Connection ID

Alternatively, specify a fixed connection ID:
export INDY_HOLDER_CONNECTION_ID="3fa85f64-5717-4562-b3fc-2c963f66afa6"

Creating Connections

1

Generate Invitation

Create an invitation from the issuer:
curl -X POST http://localhost:8021/connections/create-invitation \
  -H "Content-Type: application/json" \
  -d '{
    "my_label": "CCDigital Issuer",
    "their_label": "CCDigital Holder"
  }'
2

Accept Invitation

Accept from the holder:
curl -X POST http://localhost:8031/connections/receive-invitation \
  -H "Content-Type: application/json" \
  -d '<INVITATION_JSON>'
3

Verify Connection

Check connection state:
# On issuer
curl http://localhost:8021/connections/<CONNECTION_ID>

# On holder
curl http://localhost:8031/connections/<CONNECTION_ID>
Both should show "state": "active".

Integration with CCDigital

Java Service Classes

CCDigital integrates with ACA-Py through these service classes:

IndyAdminClient

Location: src/main/java/co/edu/unbosque/ccdigital/service/IndyAdminClient.java:30 HTTP client for ACA-Py Admin API:
// GET request
JsonNode response = indyAdminClient.get(issuerAdminUrl, "/connections");

// POST request
JsonNode result = indyAdminClient.post(issuerAdminUrl, "/schemas", schemaBody);
Features:
  • Automatic X-API-Key header injection
  • JSON request/response handling
  • RestTemplate-based HTTP client

IndyProofLoginService

Location: src/main/java/co/edu/unbosque/ccdigital/service/IndyProofLoginService.java Manages proof requests for login:
// Create proof request
String presentationExchangeId = indyProofLoginService.createProofRequest(connectionId);

// Poll for proof completion
ProofResult result = indyProofLoginService.pollProofCompletion(presentationExchangeId);

// Extract verified attributes
Map<String, String> attributes = result.getVerifiedAttributes();

UserAuthFlowService

Location: src/main/java/co/edu/unbosque/ccdigital/service/UserAuthFlowService.java Orchestrates the complete authentication flow including:
  • Proof request initiation
  • Proof verification
  • User lookup by verified attributes
  • Second factor authentication

UserAccessGovernanceService

Location: src/main/java/co/edu/unbosque/ccdigital/service/UserAccessGovernanceService.java Synchronizes user access states to ACA-Py:
// Update access state and sync to Indy
userAccessGovernanceService.updateAccessState(userId, AccessState.SUSPENDED);

Proof Polling Configuration

Configure polling behavior for proof requests:
# Poll every 1 second
export ACAPY_PROOF_POLL_INTERVAL_MS="1000"

# Timeout after 20 seconds
export ACAPY_PROOF_POLL_TIMEOUT_MS="20000"
These settings are used by IndyProofLoginService when waiting for proof completion.

Credential Issuance

From Database

CCDigital uses a Python script to issue credentials from MySQL:
# Configure script location
export INDY_TOOLS_WORKDIR="/path/to/indy-tools"
export INDY_VENV_ACTIVATE="source venv/bin/activate"
export INDY_SCRIPT="issue_credentials_from_db.py"

# Run issuance
cd $INDY_TOOLS_WORKDIR
source venv/bin/activate
python issue_credentials_from_db.py
The script:
  1. Queries eligible users from MySQL
  2. Resolves the active connection
  3. Issues credentials via ACA-Py Admin API
  4. Tracks issuance status

Manual Issuance

Issue a credential manually:
curl -X POST http://localhost:8021/issue-credential-2.0/send \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "<CONNECTION_ID>",
    "filter": {
      "indy": {
        "cred_def_id": "<CRED_DEF_ID>"
      }
    },
    "credential_preview": {
      "@type": "issue-credential/2.0/credential-preview",
      "attributes": [
        {"name": "id_type", "value": "CC"},
        {"name": "id_number", "value": "123456"},
        {"name": "first_name", "value": "Juan"},
        {"name": "last_name", "value": "Perez"},
        {"name": "email", "value": "[email protected]"}
      ]
    }
  }'

Access State Synchronization

Configuration

Enable synchronization of user access states:
export INDY_USER_ACCESS_SYNC_ENABLED="true"
export INDY_USER_ACCESS_SYNC_PATH="/connections/{conn_id}/metadata"

Metadata Structure

CCDigital writes user access state to connection metadata:
{
  "user_access_state": "ENABLED",
  "last_sync": "2026-03-07T10:30:00Z",
  "user_id": 123
}
States:
  • ENABLED: User can log in and access documents
  • SUSPENDED: User login temporarily disabled
  • DISABLED: User login permanently disabled

Admin Operations

From the Admin module, update user access state:
POST /admin/persons/{id}/access-state
{
  "accessState": "SUSPENDED"
}
This updates MySQL and synchronizes to ACA-Py metadata.

Environment Variables

Complete environment variable reference:
# Issuer Agent
export INDY_ISSUER_ADMIN_URL="http://localhost:8021"
export ACAPY_VERIFIER_ADMIN_URL="http://localhost:8021"

# Holder Agent
export INDY_HOLDER_ADMIN_URL="http://localhost:8031"
export ACAPY_HOLDER_ADMIN_URL="http://localhost:8031"

# Connection Configuration
export INDY_HOLDER_CONNECTION_ID="auto"
export INDY_HOLDER_LABEL="CCDigital Holder"

# Credential Definition
export INDY_CRED_DEF_ID="<YOUR_CRED_DEF_ID>"
export ACAPY_CRED_DEF_ID="<YOUR_CRED_DEF_ID>"

# Admin API Authentication
export INDY_ADMIN_API_KEY="your-secure-api-key"

# Proof Polling
export ACAPY_PROOF_POLL_INTERVAL_MS="1000"
export ACAPY_PROOF_POLL_TIMEOUT_MS="20000"

# Access State Sync
export INDY_USER_ACCESS_SYNC_ENABLED="true"
export INDY_USER_ACCESS_SYNC_PATH="/connections/{conn_id}/metadata"

# Python Issuance Scripts
export INDY_TOOLS_WORKDIR="/path/to/indy-tools"
export INDY_VENV_ACTIVATE="source venv/bin/activate"
export INDY_SCRIPT="issue_credentials_from_db.py"
export EXTERNAL_TOOLS_TIMEOUT_SECONDS="180"
These map to application.properties entries:
  • ccdigital.indy.* for Indy-specific configuration
  • acapy.* for legacy ACA-Py configuration
  • external-tools.indy.* for script execution

Troubleshooting

Agent Won’t Start

Check:
  • Docker logs: docker logs <container-id>
  • Wallet key is correct
  • Genesis URL is accessible
  • Ports are not already in use

Connection Fails

Verify:
  • Both agents can reach each other’s endpoints
  • Firewall rules allow traffic on configured ports
  • Agent labels are correctly configured
  • Connection invitation is valid

Credential Issuance Fails

Common issues:
  • Connection not in “active” state
  • Incorrect cred_def_id
  • Schema attributes don’t match preview
  • Holder agent not configured with --auto-respond-credential-offer

Proof Verification Fails

Check:
  • Credential was successfully issued and stored in holder wallet
  • Proof request attributes match credential schema
  • Connection is active
  • Credential definition ID matches
  • Polling timeout is sufficient

API Key Errors

If you see “Unauthorized” errors:
  1. Ensure INDY_ADMIN_API_KEY matches agent’s --admin-api-key
  2. Check that IndyAdminClient is including the X-API-Key header
  3. Verify the API key format (no extra spaces/newlines)
Always use --admin-api-key in production. Never expose admin APIs without authentication.

Production Considerations

Security

  • Use --admin-api-key for admin API authentication
  • Store wallet keys securely (secrets management system)
  • Use PostgreSQL wallet storage for reliability
  • Enable TLS for agent endpoints
  • Implement rate limiting on admin endpoints

Performance

  • Use connection pooling for database-backed wallets
  • Enable wallet caching
  • Monitor agent memory usage
  • Scale horizontally with load balancers

Monitoring

  • Collect agent logs (JSON format recommended)
  • Monitor proof request success rate
  • Track credential issuance metrics
  • Alert on failed authentication attempts

Backup and Recovery

  • Regular wallet backups
  • Document seed phrases securely
  • Test recovery procedures
  • Maintain DID recovery mechanisms

Service References

  • IndyAdminClient (src/main/java/co/edu/unbosque/ccdigital/service/IndyAdminClient.java:30)
  • IndyProofLoginService (src/main/java/co/edu/unbosque/ccdigital/service/IndyProofLoginService.java)
  • UserAuthFlowService (src/main/java/co/edu/unbosque/ccdigital/service/UserAuthFlowService.java)
  • UserAccessGovernanceService (src/main/java/co/edu/unbosque/ccdigital/service/UserAccessGovernanceService.java)
  • IndyProperties (src/main/java/co/edu/unbosque/ccdigital/config/IndyProperties.java)

Next Steps

Hyperledger Indy Setup

Complete Indy ledger setup and configuration

Hyperledger Fabric Setup

Set up Fabric for document traceability

Build docs developers (and LLMs) love