OpenAPI spec
Machine-readable API contract. Available to design partners for code generation, testing, and documentation tooling.
Available on request
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.
https://api.alloy.build/v1 ยท Auth: Bearer token ยท Format: JSON ยท Versioning: URL pathAll 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.
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..."
}
}
| Status | Code | Meaning |
|---|---|---|
400 | invalid_request | Missing or malformed parameters |
401 | authentication_failed | Missing, expired, or invalid token |
403 | insufficient_scope | Token lacks permission for this operation |
404 | not_found | Resource does not exist or is not visible to this token |
409 | conflict | Idempotency key reuse with different parameters |
422 | provider_rejected | Provider refused the operation (details include provider error) |
429 | rate_limited | Too many requests โ see rate limits |
500 | internal_error | Unexpected server error. Include request_id in support requests. |
503 | provider_unavailable | Upstream provider is unreachable or degraded |
Every response includes an X-Request-Id header. Include it in any support or debugging request.
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.
/v1/provider-connectionsCreate a new provider connection with scoped credentials.
| Parameter | Type | Description | |
|---|---|---|---|
provider | string | required | Provider identifier: fireblocks, bitgo, copper, safe |
name | string | required | Human-readable label for this connection |
credentials | object | required | Provider-specific credentials (API key, secret, workspace ID). Encrypted on receipt; never returned. |
environment | string | optional | sandbox or production. Default: sandbox |
metadata | object | optional | Arbitrary 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());
{
"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"
}
/v1/provider-connectionsList all provider connections for the current tenant. Credentials are redacted.
/v1/provider-connections/{id}Retrieve a single provider connection with capability metadata and health status.
/v1/provider-connections/{id}Deactivate a provider connection. Active transaction intents using this connection must be resolved first.
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.
/v1/walletsList canonical wallets across all connected providers, with filtering and pagination.
| Query param | Type | Description |
|---|---|---|
provider | string | Filter by provider: fireblocks, bitgo, etc. |
network | string | Filter by network: ethereum, bitcoin, solana, base |
asset | string | Filter by asset: USDC, ETH, BTC |
limit | integer | Page size (default 25, max 100) |
cursor | string | Cursor for pagination (from next_cursor in response) |
{
"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
}
/v1/wallets/{id}Retrieve a single wallet with full balance breakdown and provider reference.
/v1/wallets/{id}/addressesList all addresses for a wallet, including deposit and change addresses.
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.
/v1/transaction-intentsCreate a transaction intent. Policy and risk hooks evaluate before provider submission.
| Parameter | Type | Description | |
|---|---|---|---|
source_wallet_id | string | required | Canonical wallet ID for the source |
destination | object | required | Destination: { "type": "address", "address": "0x...", "network": "base" } or { "type": "wallet", "wallet_id": "wal_..." } |
asset | string | required | Asset symbol: USDC, ETH, BTC |
amount | string | required | Amount as decimal string (avoid floating point) |
external_ref | string | optional | Your reference ID for reconciliation (e.g., payout batch ID) |
note | string | optional | Human-readable note attached to the audit trail |
metadata | object | optional | Arbitrary 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());
{
"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"
}
/v1/transaction-intentsList transaction intents with filters for status, wallet, asset, date range, and external_ref.
/v1/transaction-intents/{id}Retrieve full intent with controls state, provider reference, timeline, and raw provider evidence.
/v1/transaction-intents/{id}/cancelCancel a pending intent. Only possible before provider submission.
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 attach approval, limit, screening, or risk results to a transaction intent. They execute as hooks before and after provider submission.
/v1/policy-decisionsAttach a policy or risk decision to a transaction intent.
| Parameter | Type | Description | |
|---|---|---|---|
transaction_intent_id | string | required | Intent to attach this decision to |
type | string | required | approval, limit_check, screening, risk_score, compliance |
result | string | required | approved, denied, review_required |
evidence | object | optional | Supporting data (limit values, screening provider response, risk score) |
decided_by | string | optional | Actor identity: user ID, service name, or policy engine reference |
{
"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"
}
/v1/policy-decisions?transaction_intent_id={id}List all policy decisions for a transaction intent, in evaluation order.
Normalized events for every state change across providers. Each event includes the canonical state, raw provider evidence, and correlation ID.
/v1/eventsList events with filters for type, resource, provider, and time range.
| Query param | Type | Description |
|---|---|---|
type | string | transaction.confirmed, wallet.synced, policy.evaluated, etc. |
resource_id | string | Filter by wallet, intent, or connection ID |
since | ISO 8601 | Return events after this timestamp |
limit | integer | Page size (default 50, max 200) |
{
"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
}
/v1/events/replayReplay events from a specific point. Returns events in chronological order with raw provider evidence for debugging missed or duplicate webhooks.
Generate finance-ready and audit-ready export records from transaction intents, policy decisions, and provider confirmations.
/v1/reconciliation-exportsCreate a reconciliation export for a date range with format and filter options.
| Parameter | Type | Description | |
|---|---|---|---|
start_date | ISO 8601 | required | Start of export period |
end_date | ISO 8601 | required | End of export period |
format | string | optional | json (default), csv |
include | array | optional | Sections: ["transactions", "policy_decisions", "provider_evidence"] |
{
"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.
Subscribe to real-time event notifications. Webhooks deliver normalized events with the same schema as the events API, plus an HMAC signature for verification.
/v1/webhooksRegister a webhook endpoint.
| Parameter | Type | Description | |
|---|---|---|---|
url | string | required | HTTPS endpoint URL |
events | array | required | Event types to subscribe: ["transaction.*", "wallet.synced"] |
secret | string | optional | HMAC signing secret. Auto-generated if omitted. |
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.
Limits are per-tenant, applied using a sliding window. Burst headers are included in every response.
| Tier | Read endpoints | Write endpoints | Reconciliation exports |
|---|---|---|---|
| Design partner | 100 req/min | 30 req/min | 10 req/hour |
| Production | 1,000 req/min | 200 req/min | 60 req/hour |
Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp). When limited, back off until Reset.
An OpenAPI 3.1 specification and client SDKs are in development for design partner validation.
Machine-readable API contract. Available to design partners for code generation, testing, and documentation tooling.
Available on request
Type-safe client with async support, automatic pagination, and webhook verification helpers.
Coming soon
TypeScript-first client with typed responses, streaming event support, and Express/Fastify webhook middleware.
Coming soon
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