Quick Start Guide
Integrate WealthOS in minutes
Sandbox keys are available immediately — no sales call required. This guide walks you through authentication, normalizing a portfolio, and firing your first transfer workflow.
Authentication
All API requests require an X-API-Key header. Generate a Sandbox key from the Developer Portal.
import { WealthOS } from "@wealthos/sdk";
const client = new WealthOS({
apiKey: process.env.WEALTHOS_API_KEY, // wos_test_write_…
environment: "sandbox",
});Key prefixes: wos_test_read_ · wos_test_write_ · wos_live_read_ · wos_live_write_
Environments
WealthOS has two isolated environments. Use Sandbox for development — it has separate data, keys, and rate limits. Promote to Live only after validating your integration.
Sandbox
Key prefix: wos_test_
Isolated data, webhooks, and rate limits.
Live
Key prefix: wos_live_
Isolated data, webhooks, and rate limits.
Normalize a Portfolio
The normalization engine ingests raw CSD data — JSON, CSV, or ISO 20022 XML — and returns a canonical NormalizedAsset[] array with resolved ISINs, inferred asset classes, and cost-basis lots.
const { data } = await client.normalize({
source_system: "ATTIJARI_CORE",
source_account_id: "ACC-88291",
source_institution: "Attijariwafa Bank",
raw_data: {
positions: [
{
isin: "MA0000012345",
name: "Attijariwafa Bank",
qty: 500,
asset_class: "equity",
currency: "MAD",
cost_price: 480.50,
},
],
},
});
// data.normalized_assets[0].asset_class === "EQUITY"
// data.normalized_assets[0].cost_basis_lots[0].unit_cost === 480.50The endpoint returns 207 Multi-Status if some positions normalize successfully while others fail — inspect errors[] in the response.
Transfer Workflow
Transfers run as Temporal.io durable workflows with 6 checkpointed states. Create a transfer, then advance it through states with PATCH.
// 1. Create
const { data: transfer } = await client.transfers.create({
source_portfolio_id: "pf-001",
target_csd: "MAROCLEAR",
assets: [{ isin: "MA0000012345", quantity: 100 }],
});
// transfer.state === "INITIATED"
// 2. Advance to PENDING_APPROVAL
await client.transfers.advance(transfer.id, {
target_state: "PENDING_APPROVAL",
note: "Sent to compliance queue",
});
// 3. Poll status
const { data: latest } = await client.transfers.get(transfer.id);
console.log(latest.state_history); // full audit trailINITIATEDPENDING_APPROVALCSD_SUBMITTEDSETTLEDCANCELLEDFAILEDWebhooks
Register a HTTPS endpoint to receive real-time event notifications. Verify the signature on every delivery.
import crypto from "crypto";
// Verify incoming webhook
export function verifyWebhook(
payload: string,
signature: string,
secret: string,
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}Error Handling
All errors follow a consistent JSON envelope. Check error for the machine-readable code and message for the human-readable description.
try {
const { data } = await client.normalize({ /* ... */ });
} catch (err) {
if (err instanceof WealthOSError) {
console.error(err.code); // "VALIDATION_FAILED"
console.error(err.message); // "source_system is required"
console.error(err.status); // 422
}
}| Code | HTTP | Meaning |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid API key |
| FORBIDDEN | 403 | Key lacks required scope |
| NOT_FOUND | 404 | Resource does not exist |
| VALIDATION_FAILED | 422 | Request body failed schema validation |
| RATE_LIMITED | 429 | Exceeded RPM limit for your plan |
| INTERNAL_ERROR | 500 | Unexpected server-side error |