Skills data, as infrastructure.
OmniSkill is a layer, not a product. Any government, NGO, training provider or employer can plug in their local labor market data, taxonomy, automation calibration and language — and get a versioned, multi-tenant API + dashboard out of the box. Country parameters are inputs, not hardcoded assumptions.
Taxonomy, occupations, labor indicators, opportunities and learning resources are CSV-ingested per context. No code change needed for a new country.
Each context (country, region, employer) owns its datasets. Row-Level Security enforces isolation across the entire stack.
Every upload is a draft version. Promotion to live atomically archives the previous version. Full audit trail in ingestion_runs.
Active contexts expose their live datasets to any client. Edits require operator membership in that context.
Concepts
REST API
All endpoints return JSON, allow CORS from any origin, and respond with a 60-second cache hint. Only datasets in status=live inside contexts in status=active are returned.
/api/v1Discovery document with endpoint index and supported kinds./api/v1/contextsList every published context./api/v1/contexts/{code}One context by code, with summary of its live datasets./api/v1/contexts/{code}/datasetsAll live datasets for a context, one per kind./api/v1/contexts/{code}/datasets/{kind}Rows for one kind. Pagination via limit (1..1000) and offset.Example response
A request for the labor indicators of the Pakistan context:
GET /api/v1/contexts/PK/datasets/labor?limit=2
{
"context": { "code": "PK", "name": "Pakistan" },
"dataset": {
"kind": "labor",
"version": 3,
"published_at": "2026-04-25T10:14:22.118Z"
},
"paging": { "limit": 2, "offset": 0, "returned": 2, "total": 184 },
"rows": [
{
"indicator_code": "youth_unemployment",
"year": 2024,
"sex": "F",
"age_band": "15-24",
"value": 11.6
},
{
"indicator_code": "labour_force_participation",
"year": 2024,
"sex": "M",
"age_band": "15-64",
"value": 67.4
}
]
}Dataset schemas
Each kind validates uploaded CSVs against a Zod schema. Field names must match exactly.
| Field | Type | Description |
|---|---|---|
| code | string | Stable identifier within the context. |
| label | string | Human-readable skill name. |
| parent_code | string? | Code of the parent skill, if any. |
| level | int | Depth in the hierarchy (0 = root). |
| source | string? | Origin (e.g. ESCO, O*NET, custom). |
| Field | Type | Description |
|---|---|---|
| code | string | Local occupation code. |
| title | string | Occupation title in the context locale. |
| isco_major | string? | ISCO-08 major group mapping. |
| automation_risk | number 0..1 | Calibrated automation exposure. |
| Field | Type | Description |
|---|---|---|
| indicator_code | string | e.g. youth_unemployment. |
| year | int | Reference year. |
| sex | M | F | T? | Optional disaggregation. |
| age_band | string? | e.g. 15-24. |
| value | number | Indicator value (units depend on indicator). |
| Field | Type | Description |
|---|---|---|
| title | string | Opportunity title. |
| employer | string? | Org or platform offering it. |
| sector | string? | Industry / sector tag. |
| region | string? | Geographic scope within the context. |
| remote | boolean | Remote-eligible flag. |
| skills | string[] | Skill codes from the live taxonomy. |
| url | string? | Application URL. |
| Field | Type | Description |
|---|---|---|
| title | string | Course or resource title. |
| provider | string? | Issuing org. |
| url | string? | Direct URL. |
| language | string? | ISO 639 code. |
| duration_h | number? | Hours to complete. |
| cost | number? | Local currency, 0 = free. |
| skill_codes | string[] | Skill codes covered. |
Dataset lifecycle
- Operator uploads a CSV via the console. Rows are validated client-side against the kind's schema; bad rows surface inline.
- On commit, a new
datasetis created withstatus=draftand the validated rows are bulk-inserted. Aningestion_runsrecord captures the audit trail. - From the deploy screen, the operator promotes a draft to
live. A trigger archives any previous live version and stampspublished_at. - The context is flipped to
active. The public dashboard and API now serve the new dataset, scoped by the URL?ctx=param or the API path.
Security model
- Roles live in a separate
user_rolestable. Role checks go through a security-definer function to avoid recursive RLS. - Operators are members of the contexts they own or edit. Writes to datasets and child tables require
can_edit_context. - Public reads are gated by a SQL predicate that requires both
contexts.status='active'anddatasets.status='live'. - Ingestion runs are append-only. The audit log cannot be edited or deleted, even by context owners.