Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Basic Usage

This chapter covers fundamental JACS operations in Python. For quick signing and verification, start with the Simplified API (jacs.simple) or JacsClient; the sections below use the JacsAgent class directly for fine-grained control.

Initializing an Agent

Create and Load Agent

import jacs

# Create a new agent instance
agent = jacs.JacsAgent()

# Load configuration from file
agent.load('./jacs.config.json')

Configuration File

Create jacs.config.json:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_default_storage": "fs",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "agent-uuid:version-uuid"
}

Creating Documents

Basic Document Creation

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create a document from JSON
document_data = {
    "title": "Project Proposal",
    "content": "Quarterly development plan",
    "budget": 50000
}

signed_document = agent.create_document(json.dumps(document_data))
print('Signed document:', signed_document)

With Custom Schema

Validate against a custom JSON Schema:

signed_document = agent.create_document(
    json.dumps(document_data),
    './schemas/proposal.schema.json'  # custom schema path
)

With Output File

signed_document = agent.create_document(
    json.dumps(document_data),
    None,                     # no custom schema
    './output/proposal.json'  # output filename
)

Without Saving

signed_document = agent.create_document(
    json.dumps(document_data),
    None,   # no custom schema
    None,   # no output filename
    True    # no_save = True
)

With Attachments

signed_document = agent.create_document(
    json.dumps(document_data),
    None,                        # no custom schema
    None,                        # no output filename
    False,                       # save the document
    './attachments/report.pdf',  # attachment path
    True                         # embed files
)

Verifying Documents

Verify Document Signature

# Verify a document's signature and hash
is_valid = agent.verify_document(signed_document_json)
print('Document valid:', is_valid)

Verify Specific Signature Field

# Verify with a custom signature field
is_valid = agent.verify_signature(
    signed_document_json,
    'jacsSignature'  # signature field name
)

Updating Documents

Update Existing Document

# Original document key format: "id:version"
document_key = 'doc-uuid:version-uuid'

# Modified document content (must include jacsId and jacsVersion)
updated_data = {
    "jacsId": "doc-uuid",
    "jacsVersion": "version-uuid",
    "title": "Updated Proposal",
    "content": "Revised quarterly plan",
    "budget": 75000
}

updated_document = agent.update_document(
    document_key,
    json.dumps(updated_data)
)

print('Updated document:', updated_document)

Update with New Attachments

updated_document = agent.update_document(
    document_key,
    json.dumps(updated_data),
    ['./new-report.pdf'],  # new attachments
    True                   # embed files
)

Signing and Verification

Sign Arbitrary Data

# Sign any string data
signature = agent.sign_string('Important message to sign')
print('Signature:', signature)

Verify Arbitrary Data

# Verify a signature on string data
is_valid = agent.verify_string(
    'Important message to sign',  # original data
    signature_base64,             # base64 signature
    public_key_bytes,             # public key as bytes
    'ring-Ed25519'                # algorithm
)

Working with Agreements

The methods in this section use the legacy jacsAgreement sidecar on an existing signed document. For new standalone consent workflows, prefer Agreement v2 through SimpleAgent.create_agreement_v2(), sign_agreement_v2(), and verify_agreement_v2().

Create an Agreement

# Add agreement requiring multiple agent signatures
document_with_agreement = agent.create_agreement(
    signed_document_json,
    ['agent1-uuid', 'agent2-uuid'],           # required signers
    'Do you agree to these terms?',            # question
    'Q1 2024 service contract',                # context
    'jacsAgreement'                            # field name
)

Sign an Agreement

# Sign the agreement as the current agent
signed_agreement = agent.sign_agreement(
    document_with_agreement_json,
    'jacsAgreement'  # agreement field name
)

Check Agreement Status

# Check which agents have signed
status = agent.check_agreement(
    document_with_agreement_json,
    'jacsAgreement'
)

print('Agreement status:', json.loads(status))

Agent Operations

Verify Agent

# Verify the loaded agent's signature
is_valid = agent.verify_agent()
print('Agent valid:', is_valid)

# Verify a specific agent file
is_valid_other = agent.verify_agent('./other-agent.json')

Update Agent

# Update agent document
updated_agent_json = agent.update_agent(json.dumps({
    "jacsId": "agent-uuid",
    "jacsVersion": "version-uuid",
    "name": "Updated Agent Name",
    "description": "Updated description"
}))

Sign External Agent

# Sign another agent's document with registration signature
signed_agent_json = agent.sign_agent(
    external_agent_json,
    public_key_bytes,
    'ring-Ed25519'
)

Generic Request/Response Documents

Not HTTP authentication: JacsAgent.sign_request() and verify_response() wrap and verify a generic signed JACS document. They do not bind the HTTP method, absolute URL, query, exact body bytes, or audience. For an authorization credential use SimpleAgent.build_request_auth_header(method, url, body, audience). For v2 response envelopes use SimpleAgent.sign_response() and fail-closed SimpleAgent.unwrap_signed_event() with pinned server keys. See Security.

Sign a Request

# Sign request-like data as a durable JACS document (not an HTTP credential)
signed_request = agent.sign_request({
    "method": "GET",
    "path": "/api/resource",
    "timestamp": datetime.now().isoformat(),
    "body": {"query": "data"}
})

Verify a Response

# Verify a generic signed document (not a jacs-response-v2 event)
result = agent.verify_response(signed_response_json)
print('Response valid:', result)

# Verify and get signer's agent ID
result_with_id = agent.verify_response_with_agent_id(signed_response_json)
print('Signer ID:', result_with_id)

Utility Functions

Hash String

import jacs

# SHA-256 hash of a string
hash_value = jacs.hash_string('data to hash')
print('Hash:', hash_value)

Create Configuration

import jacs

# Programmatically create a config JSON string
config_json = jacs.create_config(
    jacs_data_directory='./jacs_data',
    jacs_key_directory='./jacs_keys',
    jacs_agent_key_algorithm='ring-Ed25519',
    jacs_default_storage='fs'
)

print('Config:', config_json)

Error Handling

import jacs

agent = jacs.JacsAgent()

try:
    agent.load('./jacs.config.json')
except Exception as error:
    print(f'Failed to load agent: {error}')

try:
    doc = agent.create_document(json.dumps({'data': 'test'}))
    print('Document created')
except Exception as error:
    print(f'Failed to create document: {error}')

try:
    is_valid = agent.verify_document(invalid_json)
except Exception as error:
    print(f'Verification failed: {error}')

Complete Example

import jacs
import json

def main():
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Create a generic proposal document
    proposal = {
        "title": "Project Proposal",
        "description": "Q1 development plan",
        "budget": 50000
    }

    signed_proposal = agent.create_document(json.dumps(proposal))
    print('Document created')

    # Verify the document
    if agent.verify_document(signed_proposal):
        print('Document signature valid')

    # Create agreement for proposal approval
    proposal_with_agreement = agent.create_agreement(
        signed_proposal,
        ['manager-uuid', 'developer-uuid'],
        'Do you approve this proposal?'
    )

    # Sign the agreement
    signed_agreement = agent.sign_agreement(proposal_with_agreement)
    print('Agreement signed')

    # Check agreement status
    status = agent.check_agreement(signed_agreement)
    print('Status:', status)

    # Hash some data for reference
    proposal_hash = jacs.hash_string(signed_proposal)
    print('Document hash:', proposal_hash)

if __name__ == "__main__":
    main()

Working with Document Data

Parse Signed Documents

import json

# Create and sign a document
doc_data = {"title": "My Document", "content": "Hello, World!"}
signed_doc = agent.create_document(json.dumps(doc_data))

# Parse the signed document to access JACS fields
parsed = json.loads(signed_doc)
print('Document ID:', parsed.get('jacsId'))
print('Document Version:', parsed.get('jacsVersion'))
print('Signature:', parsed.get('jacsSignature'))

Document Key Format

# Document keys combine ID and version
doc_id = parsed['jacsId']
doc_version = parsed['jacsVersion']
document_key = f"{doc_id}:{doc_version}"

# Use the key for updates
updated_doc = agent.update_document(document_key, json.dumps({
    **parsed,
    "content": "Updated content"
}))

Configuration Management

Load from File

import jacs

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Environment Variables

JACS reads environment variables that override configuration file settings:

export JACS_DATA_DIRECTORY="./production_data"
export JACS_KEY_DIRECTORY="./production_keys"
export JACS_AGENT_KEY_ALGORITHM="ring-Ed25519"
export JACS_DEFAULT_STORAGE="fs"

Programmatic Configuration

import jacs
import json
import os

# Create config programmatically
config_json = jacs.create_config(
    jacs_data_directory='./jacs_data',
    jacs_key_directory='./jacs_keys',
    jacs_agent_key_algorithm='ring-Ed25519',
    jacs_default_storage='fs'
)

# Write to file
with open('jacs.config.json', 'w') as f:
    f.write(config_json)

# Then load it
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Next Steps