API Reference

WalletKit API DESIGN PARTNER PREVIEW

REST API for provider connections, normalized wallet objects, transaction intents, policy decisions, events, and reconciliation exports. All endpoints return JSON and use standard HTTP status codes.

Base URL: https://api.alloy.build/v1  ยท  Auth: Bearer token  ยท  Format: JSON  ยท  Versioning: URL path

Authentication

All API requests require a Bearer token in the Authorization header. Tokens are scoped to a single tenant and issued during onboarding. Provider-scoped tokens with reduced permissions are available for integration-specific access.

curl https://api.alloy.build/v1/wallets \
  -H "Authorization: Bearer alloy_sk_live_..." \
  -H "Content-Type: application/json"
import requests

BASE = "https://api.alloy.build/v1"
headers = {
    "Authorization": "Bearer alloy_sk_live_...",
    "Content-Type": "application/json",
}

wallets = requests.get(f"{BASE}/wallets", headers=headers)
print(wallets.json())
const BASE = "https://api.alloy.build/v1";
const headers = {
  Authorization: "Bearer alloy_sk_live_...",
  "Content-Type": "application/json",
};

const res = await fetch(`${BASE}/wallets`, { headers });
const wallets = await res.json();
console.log(wallets);

Token prefixes: alloy_sk_live_ for production, alloy_sk_test_ for sandbox. Never expose live keys in client-side code.

Error handling

All errors return a consistent JSON envelope with error.code, error.message, and an optional error.details array for validation errors.

{
  "error": {
    "code": "invalid_request",
    "message": "provider_id is required",
    "details": [
      { "field": "provider_id", "issue": "missing_required" }
    ],
    "request_id": "req_8f3a2b..."
  }
}
StatusCodeMeaning
400invalid_requestMissing or malformed parameters
401authentication_failedMissing, expired, or invalid token
403insufficient_scopeToken lacks permission for this operation
404not_foundResource does not exist or is not visible to this token
409conflictIdempotency key reuse with different parameters
422provider_rejectedProvider refused the operation (details include provider error)
429rate_limitedToo many requests โ€” see rate limits
500internal_errorUnexpected server error. Include request_id in support requests.
503provider_unavailableUpstream provider is unreachable or degraded

Every response includes an X-Request-Id header. Include it in any support or debugging request.

Provider connections

A provider connection represents a scoped integration with a custody provider (Fireblocks, BitGo, Copper, or Safe/self-hosted). Credentials are encrypted at rest and never returned in API responses.

POST /v1/provider-connections

Create a new provider connection with scoped credentials.

ParameterTypeDescription
providerstringrequiredProvider identifier: fireblocks, bitgo, copper, safe
namestringrequiredHuman-readable label for this connection
credentialsobjectrequiredProvider-specific credentials (API key, secret, workspace ID). Encrypted on receipt; never returned.
environmentstringoptionalsandbox or production. Default: sandbox
metadataobjectoptionalArbitrary key-value pairs for your internal tracking
curl -X POST https://api.alloy.build/v1/provider-connections \
  -H "Authorization: Bearer alloy_sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: conn_setup_001" \
  -d '{
    "provider": "fireblocks",
    "name": "Production Fireblocks",
    "credentials": {
      "api_key": "fb-api-...",
      "api_secret": "-----BEGIN RSA PRIVATE KEY-----\n...",
      "workspace_id": "ws_..."
    },
    "environment": "production"
  }'
conn = requests.post(f"{BASE}/provider-connections",
    headers=headers,
    json={
        "provider": "fireblocks",
        "name": "Production Fireblocks",
        "credentials": {
            "api_key": "fb-api-...",
            "api_secret": "-----BEGIN RSA PRIVATE KEY-----\n...",
            "workspace_id": "ws_..."
        },
        "environment": "production"
    }
)
print(conn.json())
const conn = await fetch(`${BASE}/provider-connections`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    provider: "fireblocks",
    name: "Production Fireblocks",
    credentials: {
      api_key: "fb-api-...",
      api_secret: "-----BEGIN RSA PRIVATE KEY-----\n...",
      workspace_id: "ws_..."
    },
    environment: "production"
  })
});
console.log(await conn.json());
Response โ€” 201 Created
{
  "id": "pconn_3kF8xQ2...",
  "provider": "fireblocks",
  "name": "Production Fireblocks",
  "environment": "production",
  "status": "active",
  "capabilities": {
    "assets": ["BTC", "ETH", "USDC", "USDT", "SOL"],
    "networks": ["ethereum", "bitcoin", "solana", "base", "polygon"],
    "signing_methods": ["mpc", "raw"]
  },
  "created_at": "2026-07-06T14:30:00Z"
}
GET /v1/provider-connections

List all provider connections for the current tenant. Credentials are redacted.

GET /v1/provider-connections/{id}

Retrieve a single provider connection with capability metadata and health status.

DELETE /v1/provider-connections/{id}

Deactivate a provider connection. Active transaction intents using this connection must be resolved first.

Wallets

Wallets are normalized representations of provider-specific vault, wallet, or account objects. Each wallet maps 1:1 to a provider construct but exposes a canonical schema with provider reference preserved.

GET /v1/wallets

List canonical wallets across all connected providers, with filtering and pagination.

Query paramTypeDescription
providerstringFilter by provider: fireblocks, bitgo, etc.
networkstringFilter by network: ethereum, bitcoin, solana, base
assetstringFilter by asset: USDC, ETH, BTC
limitintegerPage size (default 25, max 100)
cursorstringCursor for pagination (from next_cursor in response)
Response โ€” 200 OK
{
  "data": [
    {
      "id": "wal_9xK2mR...",
      "provider": "fireblocks",
      "provider_ref": { "vault_id": "12", "asset_id": "ETH" },
      "name": "Treasury ETH",
      "network": "ethereum",
      "asset": "ETH",
      "addresses": [
        { "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18", "type": "deposit" }
      ],
      "balances": {
        "available": "142.583200",
        "pending": "0.000000",
        "staked": "32.000000"
      },
      "connection_id": "pconn_3kF8xQ2...",
      "created_at": "2026-06-15T09:00:00Z",
      "synced_at": "2026-07-06T14:28:00Z"
    }
  ],
  "next_cursor": "wal_8bN3pQ...",
  "has_more": true
}
GET /v1/wallets/{id}

Retrieve a single wallet with full balance breakdown and provider reference.

GET /v1/wallets/{id}/addresses

List all addresses for a wallet, including deposit and change addresses.

Transaction intents

A transaction intent is a provider-neutral transfer request. It captures what you want to move, where, and under what controls โ€” before the provider-specific signing and approval flow begins.

POST /v1/transaction-intents

Create a transaction intent. Policy and risk hooks evaluate before provider submission.

ParameterTypeDescription
source_wallet_idstringrequiredCanonical wallet ID for the source
destinationobjectrequiredDestination: { "type": "address", "address": "0x...", "network": "base" } or { "type": "wallet", "wallet_id": "wal_..." }
assetstringrequiredAsset symbol: USDC, ETH, BTC
amountstringrequiredAmount as decimal string (avoid floating point)
external_refstringoptionalYour reference ID for reconciliation (e.g., payout batch ID)
notestringoptionalHuman-readable note attached to the audit trail
metadataobjectoptionalArbitrary key-value pairs
curl -X POST https://api.alloy.build/v1/transaction-intents \
  -H "Authorization: Bearer alloy_sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout_batch_4829" \
  -d '{
    "source_wallet_id": "wal_9xK2mR...",
    "destination": {
      "type": "address",
      "address": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
      "network": "base"
    },
    "asset": "USDC",
    "amount": "25000.00",
    "external_ref": "payout_batch_4829",
    "note": "Q3 vendor payment โ€” Acme Corp"
  }'
intent = requests.post(f"{BASE}/transaction-intents",
    headers=headers,
    json={
        "source_wallet_id": "wal_9xK2mR...",
        "destination": {
            "type": "address",
            "address": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
            "network": "base"
        },
        "asset": "USDC",
        "amount": "25000.00",
        "external_ref": "payout_batch_4829",
        "note": "Q3 vendor payment โ€” Acme Corp"
    }
)
print(intent.json())
const intent = await fetch(`${BASE}/transaction-intents`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    source_wallet_id: "wal_9xK2mR...",
    destination: {
      type: "address",
      address: "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
      network: "base"
    },
    asset: "USDC",
    amount: "25000.00",
    external_ref: "payout_batch_4829",
    note: "Q3 vendor payment โ€” Acme Corp"
  })
});
console.log(await intent.json());
Response โ€” 201 Created
{
  "id": "txn_intent_7qW4nP...",
  "status": "controls_pending",
  "source_wallet_id": "wal_9xK2mR...",
  "destination": {
    "type": "address",
    "address": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "network": "base"
  },
  "asset": "USDC",
  "amount": "25000.00",
  "external_ref": "payout_batch_4829",
  "controls": {
    "policy": { "status": "evaluating" },
    "risk": { "status": "evaluating" }
  },
  "provider_ref": null,
  "created_at": "2026-07-06T14:35:00Z",
  "updated_at": "2026-07-06T14:35:00Z"
}
GET /v1/transaction-intents

List transaction intents with filters for status, wallet, asset, date range, and external_ref.

GET /v1/transaction-intents/{id}

Retrieve full intent with controls state, provider reference, timeline, and raw provider evidence.

POST /v1/transaction-intents/{id}/cancel

Cancel a pending intent. Only possible before provider submission.

Transaction state machine

Every transaction intent progresses through a canonical state sequence. Provider-specific states are preserved in provider_ref.raw_status for debugging.

intent_created
  โ†’ controls_pending โ†’ controls_passed โ”‚ controls_failed
  โ†’ provider_submitted โ†’ provider_pending โ”‚ provider_rejected
  โ†’ broadcast โ†’ confirmed โ”‚ failed
  โ†’ cancelled (from any pre-submission state)

Policy decisions

Policy decisions attach approval, limit, screening, or risk results to a transaction intent. They execute as hooks before and after provider submission.

POST /v1/policy-decisions

Attach a policy or risk decision to a transaction intent.

ParameterTypeDescription
transaction_intent_idstringrequiredIntent to attach this decision to
typestringrequiredapproval, limit_check, screening, risk_score, compliance
resultstringrequiredapproved, denied, review_required
evidenceobjectoptionalSupporting data (limit values, screening provider response, risk score)
decided_bystringoptionalActor identity: user ID, service name, or policy engine reference
Response โ€” 201 Created
{
  "id": "pdec_2mX9rT...",
  "transaction_intent_id": "txn_intent_7qW4nP...",
  "type": "limit_check",
  "result": "approved",
  "evidence": {
    "daily_limit": "100000.00",
    "daily_used": "45000.00",
    "remaining": "55000.00"
  },
  "decided_by": "policy_engine_v2",
  "created_at": "2026-07-06T14:35:01Z"
}
GET /v1/policy-decisions?transaction_intent_id={id}

List all policy decisions for a transaction intent, in evaluation order.

Events

Normalized events for every state change across providers. Each event includes the canonical state, raw provider evidence, and correlation ID.

GET /v1/events

List events with filters for type, resource, provider, and time range.

Query paramTypeDescription
typestringtransaction.confirmed, wallet.synced, policy.evaluated, etc.
resource_idstringFilter by wallet, intent, or connection ID
sinceISO 8601Return events after this timestamp
limitintegerPage size (default 50, max 200)
Response โ€” 200 OK
{
  "data": [
    {
      "id": "evt_4nR8wQ...",
      "type": "transaction.controls_passed",
      "resource_id": "txn_intent_7qW4nP...",
      "provider": "fireblocks",
      "canonical_state": "controls_passed",
      "raw_provider_event": { "type": "TRANSACTION_APPROVED", "... ": "..." },
      "correlation_id": "corr_9xK2mR...",
      "created_at": "2026-07-06T14:35:02Z"
    }
  ],
  "next_cursor": "evt_3mQ7vP...",
  "has_more": true
}
GET /v1/events/replay

Replay events from a specific point. Returns events in chronological order with raw provider evidence for debugging missed or duplicate webhooks.

Reconciliation exports

Generate finance-ready and audit-ready export records from transaction intents, policy decisions, and provider confirmations.

POST /v1/reconciliation-exports

Create a reconciliation export for a date range with format and filter options.

ParameterTypeDescription
start_dateISO 8601requiredStart of export period
end_dateISO 8601requiredEnd of export period
formatstringoptionaljson (default), csv
includearrayoptionalSections: ["transactions", "policy_decisions", "provider_evidence"]
Response โ€” 202 Accepted
{
  "id": "rexp_6pT3wN...",
  "status": "processing",
  "period": { "start": "2026-07-01T00:00:00Z", "end": "2026-07-06T23:59:59Z" },
  "format": "csv",
  "download_url": null,
  "estimated_ready_at": "2026-07-06T14:40:00Z",
  "created_at": "2026-07-06T14:36:00Z"
}

Poll GET /v1/reconciliation-exports/{id} until status is ready and download_url is populated. URLs expire after 1 hour.

Webhooks

Subscribe to real-time event notifications. Webhooks deliver normalized events with the same schema as the events API, plus an HMAC signature for verification.

POST /v1/webhooks

Register a webhook endpoint.

ParameterTypeDescription
urlstringrequiredHTTPS endpoint URL
eventsarrayrequiredEvent types to subscribe: ["transaction.*", "wallet.synced"]
secretstringoptionalHMAC signing secret. Auto-generated if omitted.

Signature verification

Every webhook delivery includes an X-Alloy-Signature header containing an HMAC-SHA256 signature of the raw request body.

import hmac, hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
import crypto from "crypto";

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature)
  );
}

Retry policy: Failed deliveries (non-2xx) retry with exponential backoff: 30s, 2m, 15m, 1h, 6h. After 5 failures, the endpoint is disabled and a webhook.disabled event fires.

Rate limits

Limits are per-tenant, applied using a sliding window. Burst headers are included in every response.

TierRead endpointsWrite endpointsReconciliation exports
Design partner100 req/min30 req/min10 req/hour
Production1,000 req/min200 req/min60 req/hour

Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp). When limited, back off until Reset.

SDKs and OpenAPI spec

An OpenAPI 3.1 specification and client SDKs are in development for design partner validation.

OpenAPI spec

Machine-readable API contract. Available to design partners for code generation, testing, and documentation tooling.

Available on request

Python SDK

Type-safe client with async support, automatic pagination, and webhook verification helpers.

Coming soon

Node.js SDK

TypeScript-first client with typed responses, streaming event support, and Express/Fastify webhook middleware.

Coming soon

Ready to integrate?

Share your provider setup, transaction volume, and integration requirements. Design partners get sandbox access, direct engineering support, and influence on the API surface.

Questions: hello@alloy.build  ยท  Developer overview  ยท  Security & trust