Skip to main content
OmniSkill
Protocol · v1

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.

Configuration over code

Taxonomy, occupations, labor indicators, opportunities and learning resources are CSV-ingested per context. No code change needed for a new country.

Multi-tenant by design

Each context (country, region, employer) owns its datasets. Row-Level Security enforces isolation across the entire stack.

Versioned datasets

Every upload is a draft version. Promotion to live atomically archives the previous version. Full audit trail in ingestion_runs.

Public read, scoped write

Active contexts expose their live datasets to any client. Edits require operator membership in that context.

Concepts

Context
A tenant — typically a country or region (e.g. Pakistan, Ghana Urban). Holds calibration parameters, locale, currency, and ownership.
Dataset
A versioned bundle of one kind, scoped to a context. Lifecycle: draft → live → archived.
Kind
The schema family. One of taxonomy, occupations, labor, opportunities, learning.
Calibration
Per-context tunables stored on the context (e.g. automation risk weighting for the local economy).
Ingestion run
Append-only audit record for every CSV upload: rows in / ok / err, file name, validation errors, actor.

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.

GET/api/v1Discovery document with endpoint index and supported kinds.
GET/api/v1/contextsList every published context.
GET/api/v1/contexts/{code}One context by code, with summary of its live datasets.
GET/api/v1/contexts/{code}/datasetsAll live datasets for a context, one per kind.
GET/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.

Kind: taxonomy
FieldTypeDescription
codestringStable identifier within the context.
labelstringHuman-readable skill name.
parent_codestring?Code of the parent skill, if any.
levelintDepth in the hierarchy (0 = root).
sourcestring?Origin (e.g. ESCO, O*NET, custom).
Kind: occupations
FieldTypeDescription
codestringLocal occupation code.
titlestringOccupation title in the context locale.
isco_majorstring?ISCO-08 major group mapping.
automation_risknumber 0..1Calibrated automation exposure.
Kind: labor
FieldTypeDescription
indicator_codestringe.g. youth_unemployment.
yearintReference year.
sexM | F | T?Optional disaggregation.
age_bandstring?e.g. 15-24.
valuenumberIndicator value (units depend on indicator).
Kind: opportunities
FieldTypeDescription
titlestringOpportunity title.
employerstring?Org or platform offering it.
sectorstring?Industry / sector tag.
regionstring?Geographic scope within the context.
remotebooleanRemote-eligible flag.
skillsstring[]Skill codes from the live taxonomy.
urlstring?Application URL.
Kind: learning
FieldTypeDescription
titlestringCourse or resource title.
providerstring?Issuing org.
urlstring?Direct URL.
languagestring?ISO 639 code.
duration_hnumber?Hours to complete.
costnumber?Local currency, 0 = free.
skill_codesstring[]Skill codes covered.

Dataset lifecycle

  1. Operator uploads a CSV via the console. Rows are validated client-side against the kind's schema; bad rows surface inline.
  2. On commit, a new dataset is created with status=draft and the validated rows are bulk-inserted. An ingestion_runs record captures the audit trail.
  3. From the deploy screen, the operator promotes a draft to live. A trigger archives any previous live version and stamps published_at.
  4. 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_roles table. 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' and datasets.status='live'.
  • Ingestion runs are append-only. The audit log cannot be edited or deleted, even by context owners.