Simplified API

The simplified API (jacs.simple) provides a streamlined, module-level interface for common JACS operations. It's designed to get you signing and verifying in under 2 minutes.

Quick Start

Zero-config -- one call to start signing:

import jacs.simple as jacs

jacs.quickstart()
signed = jacs.sign_message({"action": "approve", "amount": 100})
result = jacs.verify(signed.raw)
print(f"Valid: {result.valid}, Signer: {result.signer_id}")

quickstart() creates a persistent agent with keys on disk. If ./jacs.config.json already exists, it loads it; otherwise it creates a new agent. Agent, keys, and config are saved to ./jacs_data, ./jacs_keys, and ./jacs.config.json. If JACS_PRIVATE_KEY_PASSWORD is not set, a secure password is auto-generated and saved to ./jacs_keys/.jacs_password. Pass algorithm="ring-Ed25519" to override the default (pq2025).

To load an existing agent explicitly, use load() instead:

agent = jacs.load("./jacs.config.json")
signed = jacs.sign_message({"action": "approve", "amount": 100})

When to Use the Simplified API

Simplified APIJacsAgent Class
Quick prototypingMultiple agents in one process
Scripts and CLI toolsComplex multi-document workflows
MCP tool implementationsFine-grained control
Single-agent applicationsCustom error handling

API Reference

quickstart(algorithm=None)

Create a persistent agent with keys on disk. If ./jacs.config.json already exists, loads it. Otherwise creates a new agent, saving keys and config to disk. If JACS_PRIVATE_KEY_PASSWORD is not set, a secure password is auto-generated and saved to ./jacs_keys/.jacs_password. Call this once before sign_message() or verify().

Parameters:

  • algorithm (str, optional): Signing algorithm. Default: "pq2025". Also: "ring-Ed25519", "RSA-PSS".

Returns: AgentInfo dataclass

info = jacs.quickstart()
print(f"Agent ID: {info.agent_id}")

# Or with a specific algorithm
info = jacs.quickstart(algorithm="ring-Ed25519")

load(config_path=None)

Load a persistent agent from a configuration file. Use this instead of quickstart() when you want to load a specific config file explicitly.

Parameters:

  • config_path (str, optional): Path to jacs.config.json (default: "./jacs.config.json")

Returns: AgentInfo dataclass

Raises: JacsError if config not found or invalid

info = jacs.load("./jacs.config.json")
print(f"Agent ID: {info.agent_id}")
print(f"Config: {info.config_path}")

is_loaded()

Check if an agent is currently loaded.

Returns: bool

if not jacs.is_loaded():
    jacs.load("./jacs.config.json")

get_agent_info()

Get information about the currently loaded agent.

Returns: AgentInfo or None if no agent is loaded

info = jacs.get_agent_info()
if info:
    print(f"Agent: {info.agent_id}")

verify_self()

Verify the loaded agent's own integrity (signature and hash).

Returns: VerificationResult

Raises: AgentNotLoadedError if no agent is loaded

result = jacs.verify_self()
if result.valid:
    print("Agent integrity verified")
else:
    print(f"Errors: {result.errors}")

sign_message(data)

Sign arbitrary data as a JACS document.

Parameters:

  • data (any): Dict, list, string, or any JSON-serializable value

Returns: SignedDocument

Raises: AgentNotLoadedError if no agent is loaded

# Sign a dict
signed = jacs.sign_message({
    "action": "transfer",
    "amount": 500,
    "recipient": "agent-123"
})

print(f"Document ID: {signed.document_id}")
print(f"Signed by: {signed.agent_id}")
print(f"Timestamp: {signed.timestamp}")
print(f"Raw JSON: {signed.raw}")

sign_file(file_path, embed=False)

Sign a file with optional content embedding.

Parameters:

  • file_path (str): Path to the file to sign
  • embed (bool, optional): If True, embed file content in the document (default: False)

Returns: SignedDocument

Raises: JacsError if file not found or no agent loaded

# Reference only (stores hash)
signed = jacs.sign_file("contract.pdf", embed=False)

# Embed content (creates portable document)
embedded = jacs.sign_file("contract.pdf", embed=True)

verify(signed_document)

Verify a signed document and extract its content.

Parameters:

  • signed_document (str): The JSON string of the signed document

Returns: VerificationResult

Raises: AgentNotLoadedError if no agent is loaded

result = jacs.verify(signed_json)

if result.valid:
    print(f"Signed by: {result.signer_id}")
    print(f"Timestamp: {result.timestamp}")
else:
    print(f"Invalid: {', '.join(result.errors)}")

verify_standalone(document, key_resolution="local", data_directory=None, key_directory=None)

Verify a signed document without loading an agent. Use when you only need to verify (e.g. a lightweight API).

Parameters: document (str|dict), key_resolution (str), data_directory (str, optional), key_directory (str, optional)

Returns: VerificationResult


audit(config_path=None, recent_n=None)

Run a read-only security audit and health checks. Returns a dict with risks, health_checks, summary, and overall_status. Does not require a loaded agent; does not modify state.

See Security Model — Security Audit for full details and options.

result = jacs.audit()
print(f"Risks: {len(result['risks'])}, Status: {result['overall_status']}")

update_agent(new_agent_data)

Update the agent document with new data and re-sign it.

This function expects a complete agent document (not partial updates). Use export_agent() to get the current document, modify it, then pass it here.

Parameters:

  • new_agent_data (dict|str): Complete agent document as JSON string or dict

Returns: str - The updated and re-signed agent document

Raises: AgentNotLoadedError if no agent loaded, JacsError if validation fails

import json

# Get current agent document
agent_doc = json.loads(jacs.export_agent())

# Modify fields
agent_doc["jacsAgentType"] = "hybrid"
agent_doc["jacsContacts"] = [{"contactFirstName": "Jane", "contactLastName": "Doe"}]

# Update (creates new version, re-signs, re-hashes)
updated = jacs.update_agent(agent_doc)
new_doc = json.loads(updated)

print(f"New version: {new_doc['jacsVersion']}")
print(f"Previous: {new_doc['jacsPreviousVersion']}")

Valid jacsAgentType values: "human", "human-org", "hybrid", "ai"


update_document(document_id, new_document_data, attachments=None, embed=False)

Update an existing document with new data and re-sign it.

Note: The original document must have been saved to disk (created without no_save=True).

Parameters:

  • document_id (str): The document ID (jacsId) to update
  • new_document_data (dict|str): Updated document as JSON string or dict
  • attachments (list, optional): List of file paths to attach
  • embed (bool, optional): If True, embed attachment contents

Returns: SignedDocument with the updated document

Raises: JacsError if document not found, no agent loaded, or validation fails

import json

# Create a document (must be saved to disk)
original = jacs.sign_message({"status": "pending", "amount": 100})

# Later, update it
doc = json.loads(original.raw)
doc["content"]["status"] = "approved"

updated = jacs.update_document(original.document_id, doc)
new_doc = json.loads(updated.raw)

print(f"New version: {new_doc['jacsVersion']}")
print(f"Previous: {new_doc['jacsPreviousVersion']}")

export_agent()

Export the current agent document for sharing or inspection.

Returns: str - The agent JSON document

Raises: AgentNotLoadedError if no agent loaded

agent_doc = jacs.export_agent()
print(agent_doc)

# Parse to inspect
agent = json.loads(agent_doc)
print(f"Agent type: {agent['jacsAgentType']}")

get_dns_record(domain, ttl=3600)

Return the DNS TXT record line for the loaded agent (for DNS-based discovery). Format: _v1.agent.jacs.{domain}. TTL IN TXT "v=hai.ai; ...".

Returns: str


get_well_known_json()

Return the well-known JSON object for the loaded agent (e.g. for /.well-known/jacs-pubkey.json). Keys: publicKey, publicKeyHash, algorithm, agentId.

Returns: dict


get_public_key()

Get the loaded agent's public key in PEM format for sharing with others.

Returns: str - PEM-encoded public key

Raises: AgentNotLoadedError if no agent loaded

pem = jacs.get_public_key()
print(pem)
# -----BEGIN PUBLIC KEY-----
# ...
# -----END PUBLIC KEY-----

Type Definitions

All types are Python dataclasses for convenient access:

AgentInfo

@dataclass
class AgentInfo:
    agent_id: str       # Agent's UUID
    config_path: str    # Path to loaded config
    public_key_path: Optional[str] = None  # Path to public key file

SignedDocument

@dataclass
class SignedDocument:
    raw: str            # Full JSON document with signature
    document_id: str    # Document's UUID (jacsId)
    agent_id: str       # Signing agent's ID
    timestamp: str      # ISO 8601 timestamp

VerificationResult

@dataclass
class VerificationResult:
    valid: bool                  # True if signature verified
    signer_id: Optional[str]     # Agent who signed
    timestamp: Optional[str]     # When it was signed
    attachments: List[Attachment]  # File attachments
    errors: List[str]            # Error messages if invalid

Attachment

@dataclass
class Attachment:
    filename: str       # Original filename
    mime_type: str      # MIME type
    hash: str           # SHA-256 hash
    embedded: bool      # True if content is embedded
    content: Optional[bytes] = None  # Embedded content (if available)

Exceptions

class JacsError(Exception):
    """Base exception for JACS errors."""
    pass

class AgentNotLoadedError(JacsError):
    """Raised when an operation requires a loaded agent."""
    pass

Complete Example

import json
import jacs.simple as jacs
from jacs.types import JacsError, AgentNotLoadedError

# Load agent
agent = jacs.load("./jacs.config.json")
print(f"Loaded agent: {agent.agent_id}")

# Verify agent integrity
self_check = jacs.verify_self()
if not self_check.valid:
    raise RuntimeError("Agent integrity check failed")

# Sign a transaction
transaction = {
    "type": "payment",
    "from": agent.agent_id,
    "to": "recipient-agent-uuid",
    "amount": 250.00,
    "currency": "USD",
    "memo": "Q1 Service Payment"
}

signed = jacs.sign_message(transaction)
print(f"Transaction signed: {signed.document_id}")

# Verify the transaction (simulating recipient)
verification = jacs.verify(signed.raw)

if verification.valid:
    doc = json.loads(signed.raw)
    print(f"Payment verified from: {verification.signer_id}")
    print(f"Amount: {doc['content']['amount']} {doc['content']['currency']}")
else:
    print(f"Verification failed: {', '.join(verification.errors)}")

# Sign a file
contract_signed = jacs.sign_file("./contract.pdf", embed=True)
print(f"Contract signed: {contract_signed.document_id}")

# Update agent metadata
agent_doc = json.loads(jacs.export_agent())
agent_doc["jacsAgentType"] = "ai"
if not agent_doc.get("jacsContacts") or len(agent_doc["jacsContacts"]) == 0:
    agent_doc["jacsContacts"] = [{"contactFirstName": "AI", "contactLastName": "Agent"}]
updated_agent = jacs.update_agent(agent_doc)
print("Agent metadata updated")

# Share public key
public_key = jacs.get_public_key()
print("Share this public key for verification:")
print(public_key)

MCP Integration

The simplified API works well with FastMCP tool implementations:

from fastmcp import FastMCP
import jacs.simple as jacs

mcp = FastMCP("My Server")

# Load agent once at startup
jacs.load("./jacs.config.json")

@mcp.tool()
def approve_request(request_id: str) -> dict:
    """Approve a request with cryptographic signature."""
    signed = jacs.sign_message({
        "action": "approve",
        "request_id": request_id,
        "approved_by": jacs.get_agent_info().agent_id
    })
    return {"signed_approval": signed.raw}

@mcp.tool()
def verify_approval(signed_json: str) -> dict:
    """Verify a signed approval."""
    result = jacs.verify(signed_json)
    return {
        "valid": result.valid,
        "signer": result.signer_id,
        "errors": result.errors
    }

Error Handling

import jacs.simple as jacs
from jacs.types import JacsError, AgentNotLoadedError

try:
    jacs.load("./missing-config.json")
except JacsError as e:
    print(f"Config not found: {e}")

try:
    # Will fail if no agent loaded
    jacs.sign_message({"data": "test"})
except AgentNotLoadedError as e:
    print(f"No agent: {e}")

try:
    jacs.sign_file("/nonexistent/file.pdf")
except JacsError as e:
    print(f"File not found: {e}")

# Verification doesn't throw - check result.valid
result = jacs.verify("invalid json")
if not result.valid:
    print(f"Verification errors: {result.errors}")

See Also