Velarum Python SDK
velarum is the official Python client for the non-custodial Agent Payment API. It wraps requests, maps every backend error to a typed exception, and — critically — never holds your keys: signing happens in a Signer you supply (HC-NC-1).
- Package:
velarum· Version:0.1.0a1(Alpha — surface may change before1.0) - Install:
pip install velarum - Requires: Python ≥ 3.10. The client is async (
httpx).
Constructing the client
VelarumClient(api_key, signer, base_url="https://api.velarumai.com", timeout=30.0). The signer is required for any flow that produces an on-chain transaction; there is no default signer — if you omit it, calls that need signing raise at call time. Use it as an async context manager so the HTTP pool closes cleanly.
import asyncio
from velarum import VelarumClient
from my_wallet import MyWalletSigner # your Signer implementation (see below)
async def main():
async with VelarumClient(api_key="vlk_test_...", signer=MyWalletSigner()) as client:
agent = await client.agents.get("agt_123")
print(agent.status)
asyncio.run(main())The Signer protocol (HC-NC-1)
A Signer is the only place a private key is ever touched, and it lives in your process — a browser wallet bridge, a hardware wallet, a mobile enclave, an MPC service, etc. Velarum invokes it but never serializes or caches the signed bytes. You must implement two methods:
from velarum import Signer, IntentSummary
class MyWalletSigner: # structurally satisfies velarum.Signer
async def sign_and_broadcast(self, chain_id: str, unsigned_tx: bytes,
intent_summary: IntentSummary) -> tuple[bytes, str]:
# Show intent_summary to the user, sign in your wallet, broadcast,
# and return (signed_tx_raw, tx_hash). Velarum keeps neither.
...
async def sign_jws(self, payload: dict) -> str:
# Return a compact JWS (header.payload.signature) for abandon /
# consent / revocation receipts, signed by the user's key.
...Errors
Every method raises a subclass of velarum.VelarumError carrying code, request_id, and details. Notable typed errors: HitlRequiredError (HC_NC_5_HITL_REQUIRED, inspect .triggered_thresholds / .next_action), AutomationConsentMissingError, IdempotencyConflictError, PaymentAbandonNotAllowedError, and the WithdrawalRequest* / SovereignAddress* family. The complete code → exception mapping is in Error codes.
Agent payments — client.payments
| Method | Purpose | Returns | Raises (notable) |
|---|---|---|---|
create(req, *, idempotency_key=None) | Register a non-custodial payment intent (intent_created) | AgentPaymentDto | HitlRequiredError, AutomationConsentMissingError, IdempotencyConflictError |
quote(req) | Route/fee preview; creates no state | PaymentQuoteResponseData | VelarumError |
get(payment_id) | Authoritative current status | AgentPaymentDto | VelarumError |
abandon(payment_id, req) | Terminate an unsettled intent with a user-signed receipt | AgentPaymentDto | PaymentAbandonNotAllowedError, AbandonReceiptInvalidError |
submit_broadcast_hint(payment_id, req) | Tell the worker which tx_hash you broadcast | AgentPaymentDto | BroadcastHintRejectedError |
watch_until_terminal(payment_id, interval=2.0) | Async-iterate status until terminal (re-fetches, never caches) | AsyncIterator[AgentPaymentDto] | VelarumError |
from velarum import CreateAgentPaymentRequest, InitiatorType, PaymentIntentDetails
dto = await client.payments.create(
CreateAgentPaymentRequest(
agent_id="agt_123",
initiator_type=InitiatorType.HUMAN,
payment_intent=PaymentIntentDetails(
amount="5.00", asset_id="base_usdc",
settlement_chain_id="base", destination="0xRecipient",
),
),
idempotency_key="order-9001",
)
async for status in client.payments.watch_until_terminal(dto.id):
print(status.status) # intent_created → chain_pending → … → settledAutomation policies (HC-NC-5)
On the client directly: create_automation_policy(req), get_automation_policy(policy_id), revoke_automation_policy(policy_id, req). The request requires all four thresholds — there is no server-side “recommended” default.
from velarum import CreateAutomationPolicyRequest, CumulativePeriod, ValidityWindow
policy = await client.create_automation_policy(
CreateAutomationPolicyRequest(
agent_id="agt_123",
single_tx_limit="10.00", cumulative_limit="100.00",
cumulative_period=CumulativePeriod.ROLLING_24H,
validity_window=ValidityWindow(start_at="2026-06-01T00:00:00Z",
end_at="2026-12-31T23:59:59Z"),
merchant_whitelist=["0xMerchant"],
consent_receipt="<JWS you signed>", idempotency_key="pol-1",
)
)Other resource namespaces
All are async and accessed off the client. Names below are the real exported resources and methods.
| Namespace | Methods |
|---|---|
client.auth | validate_api_key, validate_agent_token, admin_login, admin_verify_2fa |
client.organizations | create, get, update, create_project, list_projects, invite_member, list_members, update_member_role |
client.projects | create, list, get, update |
client.agents | create, get, get_allowance, get_status, freeze, unfreeze |
client.authorization_grants | create, list, get, update, revoke, renew |
client.policies | create, list, get, update, delete, freeze, unfreeze |
client.wallets | create, list, get, update, freeze, unfreeze, close, create_address, list_addresses_for_wallet, list_addresses |
client.subjects | create, get, update, submit_verification, verify, freeze, unfreeze, close, invite_member, list_members, accept_member, update_member, remove_member |
client.ledgers | list, get, observe |
client.accounts | create, list, get, freeze, unfreeze, close |
client.audits | list_transactions, dashboard, export, list_admin_logs |
Sovereign withdrawals — client.withdrawals (HC-NC-1)
Withdrawals are non-custodial: Velarum returns unsigned calldata for your own Safe (or equivalent) multisig; your wallet signs and broadcasts. Methods: create, list, get, broadcast_hint, abandon, get_unsigned_approve_calldata, get_unsigned_execute_calldata, watch_until_terminal, list_addresses, get_address.
from velarum import CreateWithdrawalRequest, WithdrawalReasonLabel
resp = await client.withdrawals.create(
CreateWithdrawalRequest(
source_wallet_id="wal_1", source_chain_id="base", source_asset_id="base_usdc",
amount="25.00", destination_address="0xAllowlisted",
reason_label=WithdrawalReasonLabel.PARTIAL_WITHDRAW,
)
)
# resp.unsigned_propose_calldata is UNSIGNED — hand it to your wallet to sign.
print(resp.unsigned_propose_calldata.to, resp.unsigned_propose_calldata.data)