Ownership
Two-tier (always) / four-tier (Phase A) ownership donut, board of directors, management team, optional auditor and top shareholders
The ownership endpoint backs the "Who owns this stock" panel: a donut chart, two people-list cards (BOD, Management), and two optional cards (Auditor, Top Shareholders) that light up when Phase A CSV ingest has uploaded data for the symbol.
| Method | Path | Cache TTL |
|---|---|---|
GET | /api/nepse/companies/{symbol}/fundamentals/ownership | 1 hr |
Request
No query parameters.
curl "$BASE/api/nepse/companies/NABIL/fundamentals/ownership"Response
{
"message": "ok",
"data": {
"symbol": "NABIL",
"name": "Nabil Bank Limited",
"sector": "Commercial Banks",
"tier2": {
"promoter": { "value": 51, "source": "mdp", "status": "valid" },
"public": { "value": 49, "source": "mdp", "status": "valid" }
},
"tier4": {
"fiscalYearBs": "2080/2081",
"promoter": { "value": 51, "source": "manual_swp", "status": "valid" },
"public": { "value": 31, "source": "manual_swp", "status": "valid" },
"institutional": { "value": 14, "source": "manual_swp", "status": "valid" },
"government": { "value": 4, "source": "manual_swp", "status": "valid" },
"uploadedAt": "2026-04-12T10:00:00+05:45"
},
"bod": [
{ "name": "Mr. X", "designation": "Chairperson" },
{ "name": "Mr. Y", "designation": "Director" }
],
"managementTeam": [
{ "name": "Mr. Z", "designation": "Chief Executive Officer" }
],
"auditor": {
"fiscalYearBs": "2080/2081",
"auditorName": "CA Foo",
"auditorFirm": "Foo & Associates",
"source": "manual_swp",
"uploadedAt": "2026-04-12T10:00:00+05:45"
},
"topShareholders": [
{
"fiscalYearBs": "2080/2081",
"rank": 1,
"holderName": "Foo Holdings",
"shares": 1500000,
"percent": 5.4,
"source": "manual_swp",
"uploadedAt": "2026-04-12T10:00:00+05:45"
}
],
"dataAsOf": "2026-04-12T10:00:00+05:45",
"servedAt": "2026-05-23T14:56:30+05:45"
}
}Tier resolution
The donut has two possible shapes — FE picks the richer one when both are present.
Tier2 (always present)
Backed by the MDP-denormalized promoter_percent / public_percent columns on nepse_companies. When the company-sync worker hasn't filled the row yet, both fields surface status: upstream_missing so the donut renders a tooltip rather than empty bars.
Tier4 (Phase A override)
Returned when an operator-uploaded row exists in nepse_ownership_extended for the company. The four categories sum to 100 in a clean upload, but institutional and government may carry status: upstream_missing on a partial CSV.
The tier4 object is omitted entirely (not present in the JSON) when no Phase A row exists. Don't render the four-tier donut when tier4 is absent; render the tier2 donut.
People panels
bod and managementTeam decode from the nepse_company_profiles JSONB (board_members / management_team). Each entry is {name, designation}; rows with both fields blank are dropped.
Malformed JSON falls through with a slog.Warn and returns an empty slice — the endpoint never 500s because of upstream JSON quirks.
Auditor + Top Shareholders
Both are present only when Phase A CSV ingest has populated the override tables. Until then:
auditoris omitted from the response (FE renders the "Awaiting data" state).topShareholdersis always present (possibly empty) — easier for FE to iterate over an empty slice than to null-check.
type Auditor = {
fiscalYearBs: string;
auditorName: string;
auditorFirm?: string;
source: 'manual_swp';
uploadedAt: string; // RFC3339
};
type TopShareholder = {
fiscalYearBs: string;
rank: number; // 1-indexed within FY
holderName: string;
shares?: number; // nullable — some CSVs only carry percent
percent?: number; // 0-100 scale
source: 'manual_swp';
uploadedAt: string;
};Field rules
| Field | Type | Notes |
|---|---|---|
tier2.promoter / tier2.public | MetricFloat64 | source: mdp, percentages on 0–100 |
tier4 | Tier4? | Object omitted when no Phase A override |
bod / managementTeam | PersonRow[] | Always present (possibly empty) |
auditor | Auditor? | Object omitted when no Phase A override |
topShareholders | TopShareholder[] | Always present (possibly empty) |
dataAsOf | RFC3339 NPT | Oldest signal across profile + overrides + top shareholders |
Caching
| Key | TTL |
|---|---|
nepse:fundamentals:ownership:{SYMBOL} | 1 hr |
The long TTL reflects that promoter/public changes land via the weekly company-sync worker and Phase A CSV uploads are operator-initiated — neither cadence justifies a shorter TTL.
Errors
| Code | Reason |
|---|---|
200 | OK (even when every override is empty) |
404 | Symbol not in nepse_companies |
500 | DB error |