W
WealthOS
/Quick Start

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.50

The 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 trail
INITIATED
PENDING_APPROVAL
CSD_SUBMITTED
SETTLED
CANCELLED
FAILED

Webhooks

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
  }
}
CodeHTTPMeaning
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403Key lacks required scope
NOT_FOUND404Resource does not exist
VALIDATION_FAILED422Request body failed schema validation
RATE_LIMITED429Exceeded RPM limit for your plan
INTERNAL_ERROR500Unexpected server-side error