≈ 1 day
A partner that moves client money needs an independent nightly check that its internal ledger agrees with the platform. The reconciliation report gives you a dated snapshot of every balance and position; tax lots and realized gains feed CGT statements from the same run.
The flow
| Step | Endpoint / Event | What it does |
|---|---|---|
| 1 | GET /report/reconciliation?asOf=YYYY-MM-DD | Dated snapshot: accounts, cash ledger, securities, unsettled trades, fees, dividends, corporate actions, custody positions. |
| 2 | Compare cash | Sum your internal per-user balances and match against the cash section (master + every sub-account). |
| 3 | Compare positions | Match units per symbol per sub-account against the securities section; pooled custody vs the sum of holdings is in the custody section. |
| 4 | Explain the gaps | The unsettled section is the legitimate difference between wallet cash and bank cash until T+N — everything else is an exception. |
| 5 | GET /users/{id}/tax-lots · /gains | FIFO open lots and realized disposals for capital-gains statements, from the same nightly run. |
| 6 | ?format=csv§ion=cash|securities|… | Pull any section as CSV straight into your warehouse or finance tooling. |
| 7 | GET /client-activity · GET /audit | API-level activity trail to investigate any exception you flagged. |
Implementation
const BASE = 'https://mystocks.africa/api/v1/partner';
const h = { 'x-api-key': process.env.MYSTOCKS_API_KEY };
async function nightlyRecon(asOf /* '2026-07-09' — yesterday, UTC */) {
const report = await fetch(
`${BASE}/report/reconciliation?asOf=${asOf}`, { headers: h }
).then(r => r.json());
const exceptions = [];
// 2. Cash: our ledger vs platform ledger, per sub-account
for (const row of report.cash.entries) {
const internal = await myLedger.balanceUsd(row.subAccountId, asOf);
if (Math.abs(internal - row.balanceUsd) > 0.01) {
exceptions.push({ kind: 'CASH', subAccountId: row.subAccountId, internal, platform: row.balanceUsd });
}
}
// 3. Positions: units per symbol per sub-account
for (const pos of report.securities.positions) {
const internal = await myLedger.units(pos.subAccountId, pos.symbol, asOf);
if (Math.abs(internal - pos.units) > 1e-6) {
exceptions.push({ kind: 'POSITION', ...pos, internal });
}
}
// 4. Unsettled trades explain cash-vs-bank timing — record, don't alert.
const unsettledUsd = report.summary.unsettledSellAmount + report.summary.unsettledBuyAmount;
if (exceptions.length) await alertFinance(asOf, exceptions);
await warehouse.store('mystocks_recon', { asOf, summary: report.summary, exceptions, unsettledUsd });
}Common mistakes
Prove it in sandbox first