Skip to main content
Quick Summary: Every change to the public REST API lands here with a date. Inside v1 every entry is additive — nothing here ever breaks an existing integration.

2026-09-02 — Pre-launch fixes, person skills, and skill retirement (pre-release)

Still ahead of the first public release, so the corrections below land before any integration can depend on the old shapes. Added
  • Additional skills: GET/POST/DELETE /people/{personId}/skills — assign existing catalogue skills directly to a person, list them, remove them. This closes the gap where recording a training required an assignment the API could not create. Carries skills:read / skills:write.
  • Skill retirement and delete: POST /skills/{id}/archive and /unarchive (reversible; isArchived now on the Skill object), and DELETE /skills/{id} — a soft delete guarded by a 409 with dependency counts and a ?confirm=true remedy, matching the app’s library semantics.
  • Org value delete: DELETE /org/{dimension}/{id} — removes a dimension value outright, completing the create/deactivate/delete story for all eleven dimensions. Guarded the same way: a 409 with dependency counts when people hold the value and a ?confirm=true remedy that removes the memberships; a value referenced by records outside the API (assessments, surveys, saved views) stays undeletable — deactivate it instead.
  • Scopes in the specification: every operation now declares its required scopes machine-readably (security array and x-required-scopes), and the security scheme lists the full catalogue — an admin can see exactly which scopes a key needs per endpoint.
  • A new method-not-allowed (405) problem type: a wrong verb on a real path is now distinguishable from a wrong path, and lists the allowed methods.
Changed (pre-release corrections)
  • One name per concept across request bodies: PATCH /people/{id}/access-level takes accessLevel (previously level, while POST /people already said accessLevel); career sections and skill groups use name on both create and read (previously title — while POST /careers said name).
  • Flag names are capped at 100 characters (previously the contract said 200 while storage allowed 100 — the create appeared to work and then failed).
  • Every response from any /api/v* path is now an RFC 9457 problem document — including URL typos, unknown versions (410 version-gone), and wrong verbs (405). No raw framework error body is reachable.
  • Three scopes with no endpoints behind them yet (assessments:read, certifications:read, certifications:write) are no longer grantable; they return when their resources ship.

2026-08-31 — Contract corrections and security hardening (pre-release)

The API has not yet had its first public release, so the field renames below land before any integration can depend on the old names. This is the one window in which the contract gets corrected rather than versioned. Changed (pre-release corrections)
  • One column, one public name: the career-assignment person rows now use externalId and email (previously employeeId / emailAddress) — the same field names as the Person object. The bulk sync request field is likewise externalId (previously employeeId), now capped at 20 characters to match what is actually stored.
  • requirement.scope is all or going_forward (previously goingForward) — the one enum value that had shipped outside the lowercase-snake convention.
  • GET /people/{personId}/careers now rejects limit over 200 and offset over 10,000 with a validation error, as the reference always stated.
  • Malformed ids in career-assignment request bodies are now a 400 validation error naming the field, instead of a generic 404.
  • Every create operation is documented as returning 201 (what it always did).
  • The reference now documents the {data, meta} envelope on every response, every error status with the application/problem+json shape, the standard response headers (X-Request-Id, RateLimit-*, Idempotency-Replayed), the Idempotency-Key request header on every write, and — on each operation — the scope it requires.
Hardened
  • Idempotency-Key storage is now scoped per credential, not per company: no credential can replay, collide with, or occupy a key another credential used. A retried write is only ever replayed to the credential that made it.
  • Deactivating a person disables every API credential acting as them on the very next request — and that 401 is now byte-identical to a revoked key’s, disclosing nothing about the acting person’s state.
  • Person-scoped training, learning-plan, flag and org reads are bounded by the acting identity’s span of control, exactly like the people directory: a manager-bound credential reads its span, an out-of-span person is a 404.
  • POST /people and the email-change operation return a clean 409 when the address is already registered elsewhere in SkillsDB (the address is globally unique; the conflict names no holder).
  • GET /org/manager-tree is bounded by the acting identity like every other person-scoped read: admin tiers get the full hierarchy, a manager-bound credential gets its own subtree, an employee-bound credential is refused.
  • GET /skill-groups/{id}/trainer-count and both renew-training operations return the uniform 404 for an id outside your company (previously a 200 with zero counts).

2026-08-27 — Postman collection

Added
  • A generated Postman collection covering every endpoint (154 requests across 14 folders). Authentication is Bearer at the collection level ({{apiKey}} variable) and the common headers are applied by a collection pre-request script, so every request inherits both — import, paste your key once, and play. Bodies come prefilled with working examples; writes carry a one-click optional Idempotency-Key. Regenerated with the OpenAPI artifact on every contract change.

2026-08-27 — Org structure and hardening

Added
  • Org structure (objects): the eleven dimensions at GET|POST /api/v1/org/{dimension} and PATCH /org/{dimension}/{id} (rename or deactivate — values never delete), GET /org/{dimension}/{id}/usage and /people, GET /org/dimensions, and GET /org/manager-tree — the company’s reporting hierarchy as depth-ordered, company-scoped rows. New scopes: org:read, org:write.
Hardened
  • The system-admin bypass header (nest-admin-secret) is now rejected outright on every /api/v1 request — including alongside a valid API key — instead of being ignored. The public API authenticates by API key only.

2026-08-27 — Training data and flags

Added
  • Training records (object): GET /api/v1/trainings (filter by person, skill, status, or expiry — expiredOnly=true / expiresWithinDays=N), GET /trainings/{id}, GET /trainings/{id}/evidence (time-limited links), POST /trainings (with preApproved and notApplicable), PATCH /trainings/{id}.
  • Training approval: GET /training-approvals?status= across every decidable state, POST /trainings/{id}/approve|reject|cancel (reasons required to reject/cancel), GET /trainings/{id}/potential-approvers?expertsOnly=, and per person GET /people/{id}/approval-activity (what they decided — spans superseded records) and /approval-requests (what they submitted). Approver entitlement is evaluated point-in-time and not retained on past decisions.
  • Renewal: POST /skills/{id}/renew-training (fans out — bulk bucket) with GET /skills/{id}/renew-training/preview, and the merged GET /people/{personId}/skills/{skillId}/training-log — live and superseded records together, each flagged isSuperseded.
  • Learning plans (object): GET /people/{id}/learning-plans, GET|PATCH /learning-plans/{id} (due-date and state rules enforced), PUT /learning-plans/{id}/rating (three values, null clears — and only the plan’s owner can rate), and GET|POST|DELETE /learning-items/{id}/people.
  • Flags (objects) — two independent systems: people flags (CRUD, first-class enable/disable, reorder, dependencies, guarded delete, per-person assignment, the visibility setting) and skill flags (the same plus trainer designation, four-scope assignment at /skill-flag-assignments/{scope}, and GET /skill-groups/{id}/trainer-count). Disabling a flag hides every existing assignment from reads without deleting anything — stated on the operations because reconciliation breaks otherwise.
  • New scopes: training:read, training:write, flags:read, flags:write.

2026-08-27 — People management

Added
  • Person resource (object):
    • Directory: GET /api/v1/people (bounded by the acting identity’s access level, filterable by name/email/externalId), GET /people/count, GET /people/{id}, GET /people/{id}/avatar (a resolved short-lived URL).
    • Record: POST /people (creates the authentication identity too; duplicate email → conflict), PATCH /people/{id} — a true partial update: absent means unchanged, explicit null clears. Email, access level, manager, job title and people type each move through their own operation.
    • Access levels are slugs, never integers (basic, manager, admin, fullaccess, plus three read-only legacy levels), and a grant can never exceed the acting identity’s own level (403).
    • PATCH /people/{id}/manager refuses a reporting loop with a conflict naming who and at what depth.
    • Lifecycle: POST /people/{id}/deactivate (stamps the inactive date) and /activate. There is no delete — the product’s delete is an irreversible hard delete, so deactivation is the lifecycle operation.
    • Reporting structure: GET /people/{id}/direct-reports, GET /people/{id}/span-of-control (subject included at level 0), GET /people/managers?excludeSubtreeOf=, GET /people/distribution.
    • Org schema: GET/POST (additive)/DELETE /people/{id}/org-schema, and PUT /people/{id}/org-schema/{dimension} to replace one dimension exactly — the shape an HRIS sync needs.
  • Person attributes: GET|POST|PATCH|DELETE /api/v1/job-titles and /api/v1/people-types (deletes refuse with a conflict and the holder count while people still hold them), GET /api/v1/access-levels, GET /api/v1/countries.

2026-08-27 — Skills management

Added
  • Skill resource (object):
    • GET /api/v1/skills — the company skill library, filterable by name and description. Each entry carries its usage: every career that includes it (attached directly or under a section, Required or not), how many people hold it, and whether any career including it has training dates enabled.
    • GET /api/v1/skills/{id} — one skill: name, description, and whether it is a certification.
    • POST /api/v1/skills / PATCH /api/v1/skills/{id} — create and update map exactly name and description. There is deliberately no delete — no such operation exists in the product.
    • GET /api/v1/skills/{id}/people — everyone who holds the skill, directly or through a career that includes it. Respects span of control.
    • GET /api/v1/skills/{id}/versions — name/description snapshots with editor, date, change note and the baseline marker.
    • GET /api/v1/skills/{id}/activity — a version log, not a change feed: exactly three event types (baseline, version_created, renew_training).
  • Skill Request workflow (object): GET/POST /api/v1/skill-requests, POST /api/v1/skill-requests/{id}/approve and /reject. Approving creates the library skill and assigns it to the requester; deciding an already-decided request returns a conflict.
Changed (pre-release)
  • The career definition no longer carries certifications (GET /api/v1/careers/{id}/certifications and the certifications array were removed) — certification data gets its own resource in a later release.
  • A career skill’s requirement fields were restructured into a single stored-configuration object: requirement: { isRequired, scope, effectiveFrom, setBy }. scope is all or going_forward. See the Career object for why this is a configuration, never a resolved boolean.
Both changes land before the API’s first general release, while no integration can depend on the old shape. After GA, changes of this kind would ship in a new major version instead.

2026-08-25 — Bulk people sync

Added
  • POST /api/v1/bulk/people — create-or-update up to 500 people in one call, matched by email within your company. Set isActive: false to deactivate (a soft delete: the person and their history are retained).
The batch returns 200 whenever it was processed — read each record’s status (created, updated, deactivated, failed) and the summary, not the HTTP code. One bad record never fails the rest of the batch.
Two things to design your sync around. Send an Idempotency-Key — this is the endpoint where a retry matters most; a replayed request returns the original response instead of double-creating. And email addresses are unique across SkillsDB, not per company: an address already registered to another organization is reported as a failed record rather than silently attaching to it.
Bulk sync counts against the bulk rate-limit bucket, which is deliberately far lower than the read allowance — a workforce sync is a small number of very large calls.

2026-08-25 — Career assignment

Added
  • Career assignment: who holds a career, and the operations that change it.
    • GET /api/v1/careers/{id}/people — everyone holding the career, with their level, the server-stamped assignedAt and the caller-supplied initiationDate.
    • GET /api/v1/careers/{id}/distribution — headcount grouped by level and by people-flag, aggregated server-side. A manager sees only their span of control.
    • GET /api/v1/people/{personId}/careers — every career a person holds or previously held, each marked isPrevious. Previous assignments are included by default; pass ?includePrevious=false to exclude them.
    • POST /api/v1/careers/{id}/people — assign people, each at their own level, with an optional initiationDate. Assigning is never an update: anyone already holding the career is skipped and their level is left unchanged.
    • PATCH /api/v1/careers/{id}/people/{personId} — the only way to move someone between levels of a career they already hold.
    • DELETE /api/v1/careers/{id}/people — unassign people. Career history is recorded before the live assignment is removed, so the career reappears on the person marked isPrevious.
    • DELETE /api/v1/people/{personId}/careers — clear every career one person holds, in a single call. Scoped to the named person.
Two behaviours worth designing around. Assigning fans out: beyond the assignment row it grants the learnings attached to the target level and attaches the career’s skills, so one call against many people is a large write — budget it against your rate limit accordingly. Re-assigning will not correct a level: an integration that assumes it does will silently do nothing; use the change-level operation.

2026-08-20 — v1 initial surface

Added
  • Authentication and platform: API keys (Authorization: Bearer sdb_live_...) with scopes, acting-person binding, rotation with overlap, immediate revocation. GET /api/v1/ping connectivity check. RFC 9457 problem+json errors with the error catalogue. X-Request-Id on every response. RateLimit-* headers (measurement mode). Idempotency-Key support on writes. Unauthenticated spec downloads at /api/v1/openapi.json and /api/v1/openapi.yaml.
  • Career resource: list/get/create/update/delete (dependency-guarded with ?confirm=true), full definition read, levels, sections, skill groups, skills with per-level activation and resolved benchmarks, learnings, certifications, settings (with the assessments-requires-proficiency rule), and the activity feed.
  • Career Type resource: list/get/create/update/delete (delete refuses while in use).
  • Grading Scale: read-only company scale — the rungs all benchmarks reference.