SDKs

Official Node.js and Python SDKs for integrating SignedApproval into your applications and AI agent pipelines.

Key Concepts

SignedApproval provides official SDKs for Node.js and Python that wrap the REST API in a type-safe, idiomatic interface. The SDKs handle authentication, polling, retry logic, and signature verification.

Node.js SDK

Install from npm:

bash
npm install @signedapproval/sdk

Basic usage:

typescript
import { SignedApproval } from '@signedapproval/sdk';

// The API key is a positional argument.
const sa = new SignedApproval(process.env.SIGNEDAPPROVAL_API_KEY!); // sa_live_...

// Create an approval request
const request = await sa.requestApproval({
  action: 'Deploy v2.1.0 to production',
  ttl_seconds: 3600,
  context: { environment: 'production', version: '2.1.0' },
});

console.log('Request created:', request.request_id);

// Wait for the decision (polls automatically)
const status = await sa.waitForDecision(request.request_id, {
  pollInterval: 3000,  // 3 seconds
  timeout: 300000,     // 5 minutes
});

if (status.status === 'approved' && status.decision) {
  console.log('Approved via', status.decision.method);
  // Proceed with the action
} else {
  console.log('Not approved:', status.status);
}

Verify a decision offline — check the Ed25519 signature yourself, no trust in the server:

typescript
// You hold the signed receipt (status.decision). Verify it locally.
if (status.decision) {
  const result = await sa.verifyReceipt(status.decision);
  // result.valid is true only if the signature AND bindings check out.
  console.log('Valid:', result.valid, '| decision:', result.decision);

  // Fully offline (never contacts signedapproval.net) — pin the public key:
  const pinned = await sa.verifyReceipt(status.decision, {
    pinnedPublicKeys: { 'sa-key-2026': 'MCowBQYD...base64url-SPKI...' },
  });
}

// Or ask the server to verify a decision by id (trusts the server's answer):
const server = await sa.verify(decisionId);

Python SDK

Install from PyPI:

bash
pip install signedapproval

Basic usage:

python
from signedapproval import SignedApproval

sa = SignedApproval("sa_live_...")

# Create an approval request (returns a dict)
request = sa.request_approval(
    action="Deploy v2.1.0 to production",
    ttl_seconds=3600,
    context={"environment": "production", "version": "2.1.0"},
)

print(f"Request created: {request['request_id']}")

# Wait for the decision (polls automatically)
status = sa.wait_for_decision(
    request["request_id"],
    poll_interval=3.0,  # seconds
    timeout=300.0,      # seconds
)

if status["status"] == "approved":
    print(f"Approved via {status['decision']['method']}")
else:
    print(f"Not approved: {status['status']}")

Verify a decision offline (requires pip install signedapproval[verify]):

python
# You hold the signed receipt (status["decision"]). Verify it locally.
result = sa.verify_receipt(status["decision"])
print(f"Valid: {result['valid']} | decision: {result['decision']}")

# Fully offline (never contacts the server) — pin the public key:
result = sa.verify_receipt(
    status["decision"],
    pinned_public_keys={"sa-key-2026": "MCowBQYD...base64url-SPKI..."},
)

# Or ask the server to verify by decision id (trusts the server's answer):
server = sa.verify(decision_id)

LangChain Integration

Use SignedApproval as a human-in-the-loop gate in a LangChain agent:

python
from signedapproval import SignedApproval
from langchain.tools import tool

sa = SignedApproval("sa_live_...")

@tool
def deploy_to_production(version: str) -> str:
    """Deploy the specified version to production. Requires human approval."""

    # Request approval
    request = sa.request_approval(
        action=f"Deploy {version} to production",
        ttl_seconds=300,
    )

    # Wait for human decision
    status = sa.wait_for_decision(request["request_id"], timeout=300)

    if status["status"] != "approved":
        return f"Deployment blocked: {status['status']}"

    # Proceed with deployment
    # ... your deployment logic here ...
    return f"Deployed {version} to production (approved via {status['decision']['method']})"

CrewAI Integration

Add approval gates to CrewAI agent tasks:

python
from signedapproval import SignedApproval

sa = SignedApproval(api_key="sa_live_...")

def require_approval(action: str, ttl: int = 300) -> bool:
    """Gate a high-risk action behind signed human approval."""
    request = sa.create_request(action=action, ttl_seconds=ttl)
    decision = sa.wait_for_decision(request.id, timeout=ttl)
    return decision.status == "approved"
Note
Both SDKs are open source. The Node.js SDK is in sdk/node/ and the Python SDK is in sdk/python/ within the SignedApproval repository.
Tip
The waitForDecision / wait_for_decision method handles polling automatically with configurable intervals and timeouts. For production systems, consider also setting up webhooks for immediate notifications.