Start here
Quickstart
Three requests take you from nothing to a project record with its sources and limits attached. Everything here is a plain GET.
Documentation revision: October 21, 2026. This guide describes the seven read-only routes and how to read them correctly. It does not record which hosts are live: confirm the origin as described below. The served /api/openapi.json remains the authority for field definitions/types, paths and operation IDs.
1. Confirm it is reachable
Start by asking the profile route for its headers and body. -i prints the status line and headers first.
curl -i -H "Accept: application/json" https://jason.sonderman.info/api/v1/profileYou want a 200, a Content-Type of application/json, and "example": false inside meta. A 401, sign-in HTML or a 404 means this origin is not serving the API to you.
2. Read the profile
The body has three parts: data (the profile), meta (revision and scope) and links. The links are the map of the API. Follow them instead of building URLs yourself.
{
"self": "/api/v1/profile",
"profile": "/api/v1/profile",
"experience": "/api/v1/experience",
"projects": "/api/v1/projects",
"capabilities": "/api/v1/capabilities",
"openapi": "/api/openapi.json"
}Reading responses explains what each part promises.
3. Follow a link
The projects list returns the IDs you can ask for. Right now they are prj_controltower, prj_harmony, prj_learningjourneys and prj_uplift. A project ID matches ^prj_[a-z0-9]{2,48}$; guessing one that does not exist returns a plain 404.
curl -s -H "Accept: application/json" https://jason.sonderman.info/api/v1/projects/prj_controltowerThe response is the same record the list holds, at the same revision. Keep its qualifications and attribution with it when you reuse it. Every route is described in the REST reference.
Credential-free contribution walkthrough
Run it against an origin you have confirmed as above: a local instance, or the proposed public origin once an anonymous read there succeeds. Save the source from the client example as read-contribution.mjs, then pass the bare origin:
node read-contribution.mjs https://jason.sonderman.infoFrom a repository checkout, the same file is stage1-rest/examples/read-contribution.mjs, and a local listener uses a loopback origin:
node stage1-rest/examples/read-contribution.mjs http://127.0.0.1:4347The standard-library client reads profile, follows links.experience, chooses a profile-linked work context, follows its exact relative project link, and retrieves that project. It checks status/JSON, required envelope and qualification-bearing fields, matching X-Knowledge-Revision/meta.revision, identical metadata across the sequence, reciprocal context and embedded support/source references. It retries the complete sequence at most once for mixed generations and stops on access/hold errors without retry. It then confirms empty project media and the expected generic /api/v1/media/med_absent 404. Redirects, external returned links and credentials in the supplied origin are refused.
Output retains complete returned profile/context/project objects, qualifications, attribution, evidence/source limits, revisions and request statuses. It is a client result, not an added API body field. The observation label says where the read happened: LOCAL_FUNCTIONAL_READINESS_ONLY for loopback, HOSTED_READ_NOT_RELEASE_APPROVAL for any HTTPS origin. Neither label approves a release. Runtime guards are deliberately scoped to this task/current selection, not a full JSON-Schema validator. Validate general production inputs against the unchanged schema. There is no automatic image/raw-source fallback, original-source citation invention or authentication.
The example never sends credentials and refuses redirects, so it fails on a sign-in page. Do not point it at the protected preview and describe the result as authenticated. The same page’s source example is embedded for inspection and copying; it is not a download or API route.
The Node client
This is the whole standard-library client the walkthrough runs. It defines functions and makes no request on import, sends no credentials and refuses redirects.
Read the complete client source
// Standard-library example. Importing defines functions and performs no requests.
// Execute only against a permitted local instance, or a separately approved public host.
import { pathToFileURL } from "node:url";
const PROJECT = /^\/api\/v1\/projects\/(prj_[a-z0-9]{2,48})$/;
const REVISION = /^r_[a-f0-9]{64}$/;
const need = (value, message) => { if (!value) throw new Error(message); };
const qualifications = record => {
need(Array.isArray(record.qualifications) && record.qualifications.length > 0 &&
record.qualifications.every(q => typeof q.kind === "string" && typeof q.text === "string" && q.text.length),
"Required qualification-bearing record is incomplete");
need(Number.isSafeInteger(record.revision) && record.revision > 0, "Missing record revision");
};
export async function readContribution(origin, fetcher = fetch) {
const base = new URL(origin);
const local = ["127.0.0.1", "localhost", "[::1]"].includes(base.hostname);
need(!base.username && !base.password && base.pathname === "/" && !base.search && !base.hash &&
(base.protocol === "https:" || (local && base.protocol === "http:")),
"Use a bare HTTPS origin, or HTTP loopback, without credentials/query/fragment");
const trace = [];
async function get(path, expected = 200) {
need(["/api/v1/profile", "/api/v1/experience", "/api/v1/media/med_absent"].includes(path) || PROJECT.test(path),
"Refusing an undocumented or non-relative project link");
const response = await fetcher(new URL(path, base.origin), {
method: "GET", credentials: "omit", redirect: "error", cache: "no-store",
headers: { Accept: "application/json" }, signal: AbortSignal.timeout(15000)
});
const mime = (response.headers.get("content-type") || "").split(";")[0].trim();
trace.push({ path, status: response.status, content_type: mime,
revision_header: response.headers.get("x-knowledge-revision") });
need(response.status === expected,
`Retrieval unavailable: HTTP ${response.status}; Retry-After: ${response.headers.get("retry-after") ?? "not supplied"}. No automatic retry or authentication.`);
need(mime === (expected === 200 ? "application/json" : "application/problem+json"),
"Unexpected representation; do not treat login HTML as API data");
const body = await response.json();
if (expected === 200) {
need(body.meta?.api_version === "1" && body.meta.example === false &&
REVISION.test(body.meta.revision) && typeof body.meta.coverage === "string" && body.meta.coverage.length && body.links &&
response.headers.get("x-knowledge-revision") === body.meta.revision && body.data,
"Required TEXT19 envelope/revision is incomplete or inconsistent");
}
return body;
}
// One fresh sequence retry for mixed generations only. No retry of access/hold errors.
for (let attempt = 0; attempt < 2; attempt++) {
const profile = await get("/api/v1/profile");
need(profile.data.id === "pro_jason" && Array.isArray(profile.data.experience_ids), "Missing selected profile links");
qualifications(profile.data);
need(profile.links.experience === "/api/v1/experience", "Unexpected experience link");
const experiences = await get(profile.links.experience);
need(Array.isArray(experiences.data), "Expected selected experience collection");
const experience = experiences.data.find(e => profile.data.experience_ids.includes(e.id) && e.projects?.length);
need(experience, "No linked project supplied within this selected account");
qualifications(experience);
const link = experience.projects[0];
need(PROJECT.exec(link.url)?.[1] === link.id, "Refusing an unbound or external project link");
const project = await get(link.url);
const responses = [profile, experiences, project];
if (new Set(responses.map(x => JSON.stringify(x.meta))).size !== 1) continue;
need(project.data.id === link.id && project.data.experience_ids?.includes(experience.id), "Project/context reference mismatch");
qualifications(project.data);
need(Array.isArray(project.data.assertions) && project.data.assertions.length &&
Array.isArray(project.data.evidence) && project.data.evidence.length &&
Array.isArray(project.data.sources) && project.data.sources.length, "Missing assertion/evidence/source context");
const sources = new Set(project.data.sources.map(s => s.id));
for (const source of project.data.sources) {
qualifications(source);
need(source.public_url === null && source.availability === "not_publicly_available", "Expected unavailable original source");
}
for (const assertion of project.data.assertions) {
qualifications(assertion);
need(typeof assertion.key === "string" && typeof assertion.text === "string" &&
typeof assertion.verification === "string" && assertion.attribution?.subject &&
assertion.attribution?.scope && assertion.attribution?.contribution_description &&
Object.hasOwn(assertion.attribution, "authority_limits") && Array.isArray(assertion.source_ids) &&
assertion.source_ids.length && assertion.source_ids.every(id => sources.has(id)), "Incomplete qualified assertion");
}
for (const evidence of project.data.evidence) {
qualifications(evidence);
need(evidence.attribution?.subject && evidence.support_assessment &&
Array.isArray(evidence.source_ids) && evidence.source_ids.every(id => sources.has(id)) &&
Array.isArray(evidence.supports_assertion_keys) && evidence.supports_assertion_keys.length &&
evidence.supports_assertion_keys.every(key => project.data.assertions.some(a => a.key === key)), "Incomplete evidence support");
}
need(Array.isArray(project.data.media) && project.data.media.length === 0, "This example expects the current empty media selection");
const media = await get("/api/v1/media/med_absent", 404);
need(media.status === 404 && media.code === "not_found" && media.detail === "No resource is available at this address.",
"Expected generic unavailable-media Problem");
// Client result, NOT a new API response schema. Preserve complete returned objects.
return { observation: local ? "LOCAL_FUNCTIONAL_READINESS_ONLY" : "HOSTED_READ_NOT_RELEASE_APPROVAL",
origin: base.origin, meta: profile.meta, profile: profile.data, experience,
project: project.data, media_observation: media, requests: trace };
}
throw new Error("Consistent current representation unavailable after one mixed-generation retry");
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
if (process.argv.length !== 3) throw new Error("Usage: node stage1-rest/examples/read-contribution.mjs <permitted-origin>");
console.log(JSON.stringify(await readContribution(process.argv[2]), null, 2));
} catch (error) { console.error(error.message); process.exitCode = 1; }
}
JavaScript in an authorized same-origin browser
Complete browser example reads profile, projects and capabilities without embedding credentials. It accepts only selected same-origin GET paths, uses the existing same-origin browser session, checks status/media type before parsing JSON, and rejects a mixed-revision result after one fresh retry. Run it from the docs page of the origin you are reading, or from a local counterpart. A server-side process has no browser session, and the example does not create one.
const response = await fetch('/api/v1/profile', {
method: 'GET',
credentials: 'same-origin',
cache: 'no-store',
redirect: 'error',
headers: { Accept: 'application/json' }
});
if (!response.ok) throw new Error(`Access or API request failed: ${response.status}`);
if ((response.headers.get('content-type') || '').split(';')[0].trim() !== 'application/json') {
throw new Error('Expected application/json; check access and environment');
}
const profile = await response.json();
console.log(profile.data, profile.meta, profile.links);Keep complete qualification-bearing objects when presenting or reasoning over a response. Before production client use, validate against the unchanged schema/OpenAPI rather than treating the short example’s checks or generated TypeScript aliases as full validation. Examples are validated offline against the existing handler and approved response data. They do not establish unattended protected-preview access.
The browser example
This example defines functions only. Run it by hand from a page on the origin you are reading. It does not supply sign-in, schema validation or proof that an origin is live.
Read the complete browser example
// Documentation sample only. Importing defines functions; it performs no request.
// Use only in an authorized same-origin browser. No CLI/agent login is supplied.
const allowed = /^\/api\/(?:openapi\.json|v1\/(?:profile|experience|projects|capabilities|projects\/prj_[a-z0-9]{2,48}|media\/med_[a-z0-9]{2,48}))$/;
export async function readJSON(path) {
if (typeof path !== 'string' || !allowed.test(path)) {
throw new Error('Only documented relative GET paths are accepted');
}
if (!globalThis.location?.origin) {
throw new Error('This sample requires an authorized same-origin browser context');
}
const target = new URL(path, globalThis.location.origin);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000);
try {
const response = await fetch(target, {
method: 'GET',
credentials: 'same-origin',
redirect: 'error',
cache: 'no-store',
headers: { Accept: 'application/json' },
signal: controller.signal
});
const mime = (response.headers.get('content-type') || '').split(';')[0].trim();
if (!response.ok) {
const error = new Error(response.status === 401 || response.status === 403
? 'Private access is unavailable in this browser context'
: `Request unavailable: HTTP ${response.status}`);
error.status = response.status;
// An upstream access page need not be JSON. Do not log or parse it as API data.
if (mime === 'application/problem+json') {
const problem = await response.json();
error.code = typeof problem.code === 'string' ? problem.code : undefined;
}
throw error;
}
if (mime !== 'application/json') {
throw new Error('Expected JSON; verify access and selected environment');
}
const body = await response.json();
if (path !== '/api/openapi.json' && (
!Object.hasOwn(body, 'data') || body.meta?.api_version !== '1' || !body.links ||
!/^r_[a-f0-9]{64}$/.test(body.meta.revision) || typeof body.meta.example !== 'boolean' || response.headers.get('x-knowledge-revision') !== body.meta.revision
)) throw new Error('Unexpected business response envelope');
// Deliberately not a full schema validator: validate using the unchanged contract.
return body;
} finally {
clearTimeout(timeout);
}
}
export async function readBundle() {
for (let attempt = 0; attempt < 2; attempt++) {
const [profile, projects, capabilities] = await Promise.all([
readJSON('/api/v1/profile'),
readJSON('/api/v1/projects'),
readJSON('/api/v1/capabilities')
]);
const responses = [profile, projects, capabilities];
if (new Set(responses.map(x => x.meta.revision)).size === 1 &&
new Set(responses.map(x => x.meta.example)).size === 1 &&
new Set(responses.map(x => x.meta.coverage)).size === 1) {
// Preserve complete qualifier-bearing objects; do not flatten claims to strings.
return { profile, projects, capabilities };
}
}
throw new Error('Consistent current representation unavailable after one retry');
}
// Example usage, invoked manually in an authorized context:
// const account = await readBundle();
// Inspect account.projects.data[].assertions alongside evidence/sources/qualifications.