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:
npm install @signedapproval/sdkBasic usage:
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:
// 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:
pip install signedapprovalBasic usage:
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]):
# 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:
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:
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.Related Articles