Drive Mountspec from your own code
The page does its free work in the browser: it reads the pasted Vue unit for its public surface, recommends the
unit-test-vue-pinia skill's Pinia setup and mount, and lints a pasted test file. Its two paid lanes are available over
HTTP. You send one JSON object with task set to "write" or "review", and the
model replies with one JSON object. The easiest way to build the input is the page itself:
VueKit.buildInput(VueKit.analyze({title, source, tests, focus}), lane) in vuekit.js,
which also runs under Node; Recon.reconcile(reply, {A, K: VueKit, lane, sent}) in recon.js
runs the page's checks on a normalised reply.
Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends
Content-Type: application/json and, once you have a token,
Authorization: Bearer <token>. Both lanes run on gpt-terra with a 10%
publisher markup. A run needs a signed-in (user) token and is metered: the platform holds
the estimate's hold_credits while the run is in flight and then charges only
what it used (charged_credits). /estimate is free. A plain-text summary is at
/llms.txt.
The response envelope
Every response is {"data": ..., "error": null} on success or {"data": null, "error": {"code", "message"}} on failure.
Error codes
| HTTP | code | meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | The body is not a JSON object, a required field is missing, or a field is the wrong type. Every field of the run input is a string. |
| 401 | UNAUTHORIZED | No token, or an expired one: get a new one (step 2). |
| 402 | INSUFFICIENT_CREDITS | The balance is below the run's minimum. Top up at skillsafe.ai/account/billing/. |
| 403 | FORBIDDEN | A guest token tried a metered run: sign in for a personal token. |
| 404 | NOT_FOUND | Unknown job id. |
| 429 | RATE_LIMITED | Back off and retry with the same Idempotency-Key. |
The task field
Two lanes over the same unit:
"write"- create a Vitest file: the behaviour boundary, the Pinia strategy and mount, three to ten cases each named for the behaviour it proves and covering surface ids fromfacts.surface, the complete file as an array of lines with oneit()per case, and the gaps it leaves open."review"- review the existing test file: a keep / tighten / rewrite verdict, severity-ranked findings with the rule, the line, the problem and the fix, the accepted minimal Pinia setups (never flagged), the missing coverage and the brittleness risks.
Input fields (all strings)
| field | when | content |
|---|---|---|
task | required | write or review |
title | required | The unit's name, e.g. TodoForm. |
source | required | The code under test, up to 12,000 characters (the page cuts lines from the middle beyond that and marks the cut). Several files are separated by // file: path lines. |
tests | review only | The existing test file, up to 14,000 characters; line 1 is its first line. |
focus | optional | What to cover or look at, up to 2,000 characters. |
facts | required | A JSON string: unit{kind, name}, surface[] (ids like emit:saved, action:addTodo), async, timers[], router, children[], pinia{strategy, why}, mount; review adds tests[{name, line, expects}], lint[{rule, line, test, text}], accepted[{line, setup}], untested[]; and clip. |
retry_note | optional | Only on a reformat retry: what was wrong with the previous reply. |
The reply contract
write: task, task_note, unit{kind, name}, boundary,
pinia{strategy: testing_pinia|testing_pinia_real_actions|real_pinia|active_testing_pinia|none, why},
mount: shallowMount|mount|none, assumptions[], cases[{id, name, covers[], asserts}] (three to ten),
test_file{path, code[]} (one string per line), gaps[], headline, summary.
review: task, task_note, unit{kind, name}, verdict: keep|tighten|rewrite,
findings[{id, severity: high|medium|low, rule, test, line, problem, fix, code[]}] (at most ten),
accepted[{line, setup, why}], coverage_gaps[{covers, behaviour, why}], brittleness[],
pinia{strategy, why}, headline, summary.
Worked examples
These are the page's own bundled examples (invented components and stores), with the input as app.js builds it and the saved model reply the page replays for free - two for write, one for review.
write: a TodoForm component that uses a Pinia store
Input (the source shown separately):
{
"task": "write",
"title": "TodoForm",
"source": "(a string - shown below)",
"facts": "(a JSON string; decoded: a component, Pinia testing_pinia, mount shallowMount; surface prop:placeholder, prop:maxLength, emit:saved, emit:cancel, action:addTodo, store:useTodoStore)"
}
source:
<!-- file: src/components/TodoForm.vue -->
<script setup lang="ts">
import { ref, computed } from "vue";
import { useTodoStore } from "@/stores/todos";
import PriorityPicker from "./PriorityPicker.vue";
const props = withDefaults(defineProps<{
placeholder?: string;
maxLength?: number;
}>(), { placeholder: "What needs doing?", maxLength: 80 });
const emit = defineEmits<{
(e: "saved", title: string): void;
(e: "cancel"): void;
}>();
const todos = useTodoStore();
const title = ref("");
const priority = ref<"low" | "high">("low");
const tooLong = computed(() => title.value.length > props.maxLength);
function submit() {
const clean = title.value.trim();
if (!clean || tooLong.value) return;
todos.addTodo({ title: clean, priority: priority.value });
emit("saved", clean);
title.value = "";
}
</script>
<template>
<form class="todo-form" @submit.prevent="submit">
<input v-model="title" :placeholder="placeholder" data-test="title" />
<PriorityPicker v-model="priority" />
<p v-if="tooLong" class="error" data-test="too-long">Keep it under {{ maxLength }} characters.</p>
<p data-test="open-count">{{ todos.openCount }} open</p>
<button type="submit" :disabled="tooLong">Add</button>
<button type="button" data-test="cancel" @click="emit('cancel')">Cancel</button>
</form>
</template>
Reply:
{
"task": "write",
"task_note": "",
"unit": {
"kind": "component",
"name": "TodoForm"
},
"boundary": "Component UI behaviour of TodoForm: what it emits, which store action it calls with what payload, and what it renders, with PriorityPicker stubbed because its own behaviour is not the subject.",
"pinia": {
"strategy": "testing_pinia",
"why": "TodoForm only needs to prove that addTodo was called with the right payload, so createTestingPinia({ createSpy: vi.fn }) stubs and spies the action; the openCount getter is overridden on the testing store for the render case."
},
"mount": "shallowMount",
"assumptions": [
"The spec sits next to the component at src/components/TodoForm.spec.ts, importing ./TodoForm.vue and ./PriorityPicker.vue, with the @ alias resolving to src as in the source.",
"PriorityPicker was not pasted; shallowMount stubs it and the tests fire its v-model update with findComponent(PriorityPicker).vm.$emit(\"update:modelValue\", ...), assuming it uses the default modelValue model.",
"The todos store was not pasted; openCount is assumed to be a getter (or state) that the testing store lets the test assign directly."
],
"cases": [
{
"id": "T1",
"name": "emits saved with the trimmed title when the form is submitted",
"covers": [
"emit:saved"
],
"asserts": "wrapper.emitted(\"saved\") holds exactly one payload, the trimmed title \"Buy milk\"."
},
{
"id": "T2",
"name": "adds the todo to the store with the trimmed title and low priority by default",
"covers": [
"action:addTodo",
"store:useTodoStore"
],
"asserts": "The stubbed addTodo spy is called once with { title: \"Buy milk\", priority: \"low\" }."
},
{
"id": "T3",
"name": "adds the todo with the priority chosen in the priority picker",
"covers": [
"action:addTodo"
],
"asserts": "After the stubbed PriorityPicker emits update:modelValue \"high\", addTodo receives priority \"high\"."
},
{
"id": "T4",
"name": "clears the title input after a successful save",
"covers": [
"emit:saved"
],
"asserts": "The title input's value is empty after a valid submit."
},
{
"id": "T5",
"name": "does not save or emit when the title is blank",
"covers": [
"emit:saved",
"action:addTodo"
],
"asserts": "A whitespace-only title calls no addTodo and emits no saved event."
},
{
"id": "T6",
"name": "shows the too-long error and blocks saving when the title exceeds maxLength",
"covers": [
"prop:maxLength",
"action:addTodo"
],
"asserts": "With maxLength 5 and a 6-character title the error text reads \"Keep it under 5 characters.\", the submit button is disabled, and submitting saves and emits nothing."
},
{
"id": "T7",
"name": "renders the placeholder prop on the title input, falling back to the default",
"covers": [
"prop:placeholder"
],
"asserts": "The input placeholder is \"What needs doing?\" by default and \"Add a task\" after setProps."
},
{
"id": "T8",
"name": "emits cancel when the cancel button is clicked",
"covers": [
"emit:cancel"
],
"asserts": "Clicking the cancel button emits cancel exactly once."
},
{
"id": "T9",
"name": "renders the open todo count from the store",
"covers": [
"store:useTodoStore"
],
"asserts": "Overriding openCount to 3 on the testing store renders \"3 open\"."
}
],
"test_file": {
"path": "src/components/TodoForm.spec.ts",
"code": [
"import { describe, it, expect, beforeEach, vi } from \"vitest\";",
"import { shallowMount, type VueWrapper } from \"@vue/test-utils\";",
"import { createTestingPinia } from \"@pinia/testing\";",
"import { nextTick } from \"vue\";",
"import TodoForm from \"./TodoForm.vue\";",
"import PriorityPicker from \"./PriorityPicker.vue\";",
"import { useTodoStore } from \"@/stores/todos\";",
"",
"function mountForm(props: Record<string, unknown> = {}) {",
" return shallowMount(TodoForm, {",
" props,",
" global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },",
" });",
"}",
"",
"describe(\"TodoForm\", () => {",
" let wrapper: VueWrap
... (shortened here; the page shows it whole)
write: a cart store with getters, actions and a coupon
Input (the source shown separately):
{
"task": "write",
"title": "useCartStore",
"source": "(a string - shown below)",
"facts": "(a JSON string; decoded: a store, Pinia real_pinia, mount none; surface state:lines, state:couponCode, getter:itemCount, getter:subtotal, getter:discount, getter:total, action:add, action:remove, action:applyCoupon, action:clear)"
}
source:
// file: src/stores/cart.ts
import { defineStore } from "pinia";
export interface CartLine {
sku: string;
name: string;
unitPrice: number; // in cents
qty: number;
}
export const useCartStore = defineStore("cart", {
state: () => ({
lines: [] as CartLine[],
couponCode: null as string | null,
}),
getters: {
itemCount: (state) => state.lines.reduce((n, l) => n + l.qty, 0),
subtotal: (state) => state.lines.reduce((sum, l) => sum + l.unitPrice * l.qty, 0),
discount(): number {
return this.couponCode === "SAVE10" ? Math.round(this.subtotal / 10) : 0;
},
total(): number {
return this.subtotal - this.discount;
},
},
actions: {
add(line: Omit<CartLine, "qty">, qty = 1) {
const found = this.lines.find((l) => l.sku === line.sku);
if (found) found.qty += qty;
else this.lines.push({ ...line, qty });
},
remove(sku: string) {
this.lines = this.lines.filter((l) => l.sku !== sku);
},
applyCoupon(code: string) {
if (code.trim().toUpperCase() !== "SAVE10") return false;
this.couponCode = "SAVE10";
return true;
},
clear() {
this.lines = [];
this.couponCode = null;
},
},
});
Reply:
{
"task": "write",
"task_note": "",
"unit": {
"kind": "store",
"name": "useCartStore"
},
"boundary": "Store behaviour only: state transitions and getter values driven through the real actions, which needs no component or mounting.",
"pinia": {
"strategy": "real_pinia",
"why": "A pure store test with no dependent stores, so setActivePinia(createPinia()) in beforeEach gives a fresh real store per test and lets the real actions run."
},
"mount": "none",
"assumptions": [
"The spec sits one folder below the project root, so the store is imported from ../src/stores/cart.",
"Prices are integer cents as the source comments, so expected totals are whole numbers."
],
"cases": [
{
"id": "T1",
"name": "adds a new line with a quantity of one by default",
"covers": [
"action:add",
"state:lines"
],
"asserts": "lines holds one entry with the given sku, name, unitPrice and qty 1"
},
{
"id": "T2",
"name": "increases the quantity instead of duplicating a line when the same sku is added again",
"covers": [
"action:add",
"getter:itemCount"
],
"asserts": "lines keeps one entry whose qty is 3 and itemCount is 3"
},
{
"id": "T3",
"name": "removes only the line with the given sku",
"covers": [
"action:remove",
"state:lines"
],
"asserts": "lines keeps the other sku and itemCount drops accordingly"
},
{
"id": "T4",
"name": "computes subtotal and total in cents from unit price times quantity",
"covers": [
"getter:subtotal",
"getter:total"
],
"asserts": "subtotal and total both equal 1005 with no coupon"
},
{
"id": "T5",
"name": "accepts a trimmed lowercase SAVE10 coupon and takes ten percent off rounded to the cent",
"covers": [
"action:applyCoupon",
"state:couponCode",
"getter:discount"
],
"asserts": "applyCoupon returns true, couponCode is SAVE10, discount is 101 and total is 904"
},
{
"id": "T6",
"name": "rejects an unknown coupon and leaves the discount at zero",
"covers": [
"action:applyCoupon",
"getter:discount"
],
"asserts": "applyCoupon returns false, couponCode stays null and discount is 0"
},
{
"id": "T7",
"name": "clears every line and the applied coupon",
"covers": [
"action:clear",
"state:couponCode"
],
"asserts": "lines is empty, couponCode is null and total is 0"
},
{
"id": "T8",
"name": "reports zero count and totals for an empty cart",
"covers": [
"getter:itemCount",
"getter:total"
],
"asserts": "itemCount, subtotal, discount and total are all 0"
}
],
"test_file": {
"path": "cart.spec.ts",
"code": [
"import { describe, it, expect, beforeEach } from \"vitest\";",
"import { setActivePinia, createPinia } from \"pinia\";",
"import { useCartStore } from \"../src/stores/cart\";",
"",
"const pen = { sku: \"PEN\", name: \"Pen\", unitPrice: 250 };",
"const book = { sku: \"BOOK\", name: \"Book\", unitPrice: 505 };",
"",
"describe(\"useCartStore\", () => {",
" let cart: ReturnType<typeof useCartStore>;",
"",
" beforeEach(() => {",
" setActivePinia(createPinia());",
" cart = useCartStore();",
" });",
"",
" it(\"adds a new line with a quantity of one by default\", () => {",
" cart.add(pen);",
"",
" expect(cart.lines).toEqual([{ sku: \"PEN\", name: \"Pen\", unitPrice: 250, qty: 1 }]);",
" });",
"",
" it(\"increases the quantity instead of duplicating a line when the same sku is added again\", () => {",
" cart.add(pen);",
" cart.add(pen, 2);",
"",
" expect(cart.lines).toHaveLength(1);",
" expect(cart.lines[0].qty).toBe(3);",
" expect(cart.itemCount).toBe(3);",
" });",
"",
" it(\"removes only the line with the given sku\", () => {",
" cart.add(pen);",
" cart.add(book, 2);",
"",
" cart.remove(\"PEN\");",
"",
" expect(cart.lines.map((l) => l.sku)).toEqual([\"BOOK\"]);",
" expect(cart.itemCount).toBe(2);",
" });",
"",
" it(\"computes subtotal and total in cents from unit price times quantity\", () => {",
" cart.add(pen, 2);",
"
... (shortened here; the page shows it whole)
review: a TodoForm spec with seven lint findings and three accepted Pinia setups
Input (the source and the tests shown separately):
{
"task": "review",
"title": "TodoForm",
"source": "(a string - shown below)",
"tests": "(a string - shown below)",
"facts": "(a JSON string; decoded: a component, Pinia testing_pinia, mount shallowMount; 5 tests, lint async_update, wrapper_vm, snapshot, brittle_selector, determinism, mock_reset, no_assertion, accepted setups at lines 12, 31, 45, untested prop:placeholder, emit:cancel)"
}
source:
<!-- file: src/components/TodoForm.vue -->
<script setup lang="ts">
import { ref, computed } from "vue";
import { useTodoStore } from "@/stores/todos";
import PriorityPicker from "./PriorityPicker.vue";
const props = withDefaults(defineProps<{
placeholder?: string;
maxLength?: number;
}>(), { placeholder: "What needs doing?", maxLength: 80 });
const emit = defineEmits<{
(e: "saved", title: string): void;
(e: "cancel"): void;
}>();
const todos = useTodoStore();
const title = ref("");
const priority = ref<"low" | "high">("low");
const tooLong = computed(() => title.value.length > props.maxLength);
function submit() {
const clean = title.value.trim();
if (!clean || tooLong.value) return;
todos.addTodo({ title: clean, priority: priority.value });
emit("saved", clean);
title.value = "";
}
</script>
<template>
<form class="todo-form" @submit.prevent="submit">
<input v-model="title" :placeholder="placeholder" data-test="title" />
<PriorityPicker v-model="priority" />
<p v-if="tooLong" class="error" data-test="too-long">Keep it under {{ maxLength }} characters.</p>
<p data-test="open-count">{{ todos.openCount }} open</p>
<button type="submit" :disabled="tooLong">Add</button>
<button type="button" data-test="cancel" @click="emit('cancel')">Cancel</button>
</form>
</template>
tests:
import { describe, it, expect, vi } from "vitest";
import { mount } from "@vue/test-utils";
import { createTestingPinia } from "@pinia/testing";
import TodoForm from "../TodoForm.vue";
import { useTodoStore } from "@/stores/todos";
vi.mock("@/analytics", () => ({ track: vi.fn() }));
describe("TodoForm", () => {
it("sets title on input", async () => {
const wrapper = mount(TodoForm, {
global: { plugins: [createTestingPinia({ initialState: { todos: { items: [] } } })] },
});
wrapper.find("input").setValue("Buy milk");
expect(wrapper.vm.title).toBe("Buy milk");
});
it("submits", async () => {
const wrapper = mount(TodoForm, {
global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
});
const store = useTodoStore();
await wrapper.find("input").setValue(" Buy milk ");
await wrapper.find("form").trigger("submit");
expect(store.addTodo).toHaveBeenCalledWith({ title: "Buy milk", priority: "low" });
expect(wrapper.emitted("saved")?.[0]?.[0]).toBe("Buy milk");
expect(wrapper.find("input").element.value).toBe("");
});
it("matches the snapshot", () => {
const wrapper = mount(TodoForm, { global: { plugins: [createTestingPinia()] } });
expect(wrapper.html()).toMatchSnapshot();
});
it("shows the error", async () => {
const wrapper = mount(TodoForm, {
props: { maxLength: 5 },
global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
});
await wrapper.find("input").setValue("A".repeat((Date.now() % 10) + 6));
expect(wrapper.find("form > p:nth-child(3)").exists()).toBe(true);
});
it("cancel button", async () => {
const wrapper = mount(TodoForm, { global: { plugins: [createTestingPinia()] } });
await wrapper.findAll("button")[1].trigger("click");
});
});
Reply:
{
"task": "review",
"task_note": "",
"unit": {
"kind": "component",
"name": "TodoForm"
},
"verdict": "rewrite",
"findings": [
{
"id": "F1",
"severity": "high",
"rule": "wrapper_vm",
"test": "sets title on input",
"line": 15,
"problem": "The test reads the internal ref wrapper.vm.title, so it proves an implementation detail rather than anything a user sees and breaks if the ref is renamed.",
"fix": "Assert the input's DOM value, or better, fold this into a behaviour such as submitting the typed title and checking the saved payload.",
"code": [
"await wrapper.find(\"[data-test=title]\").setValue(\"Buy milk\");",
"expect((wrapper.find(\"[data-test=title]\").element as HTMLInputElement).value).toBe(\"Buy milk\");"
]
},
{
"id": "F2",
"severity": "high",
"rule": "determinism",
"test": "shows the error",
"line": 40,
"problem": "The input length is derived from Date.now(), so every run types a different string and a failure cannot be reproduced.",
"fix": "Use a fixed value one character over the limit; the component reads no clock, so no fake timers are needed.",
"code": [
"await wrapper.find(\"[data-test=title]\").setValue(\"A\".repeat(6));"
]
},
{
"id": "F3",
"severity": "high",
"rule": "no_assertion",
"test": "cancel button",
"line": 44,
"problem": "The test clicks the button and asserts nothing, so it passes even if the cancel event is never emitted.",
"fix": "Click the data-test cancel hook and assert the cancel emission, and name the test for that behaviour.",
"code": [
"it(\"emits cancel when the cancel button is clicked\", async () => {",
" await wrapper.find(\"[data-test=cancel]\").trigger(\"click\");",
" expect(wrapper.emitted(\"cancel\")).toHaveLength(1);",
"});"
]
},
{
"id": "F4",
"severity": "medium",
"rule": "async_update",
"test": "sets title on input",
"line": 14,
"problem": "setValue is not awaited, so the assertion on the next line can run before Vue has updated.",
"fix": "Await every setValue and trigger call.",
"code": []
},
{
"id": "F5",
"severity": "medium",
"rule": "snapshot",
"test": "matches the snapshot",
"line": 32,
"problem": "A full-HTML snapshot of a dynamic form fails on any markup change and does not state which behaviour matters.",
"fix": "Replace it with targeted assertions, for example the default placeholder text and the open count rendered from the store.",
"code": [
"expect(wrapper.find(\"[data-test=title]\").attributes(\"placeholder\")).toBe(\"What needs doing?\");"
]
},
{
"id": "F6",
"severity": "medium",
"rule": "brittle_selector",
"test": "shows the error",
"line": 41,
"problem": "The selector form > p:nth-child(3) depends on element position, so reordering the template breaks the test without a behaviour change.",
"fix": "Use the existing data-test hook and assert the message text.",
"code": [
"expect(wrapper.find(\"[data-test=too-long]\").text()).toContain(\"Keep it under 5 characters.\");"
]
},
{
"id": "F7",
"severity": "medium",
"rule": "one_behaviour",
"test": "submits",
"line": 18,
"problem": "One test checks the store action call, the saved emission and the input reset, so a failure does not say which behaviour broke.",
"fix": "Split it into three tests: calls addTodo with the trimmed title and priority, emits saved with the trimmed title, and clears the input after a save.",
"code": []
},
{
"id": "F8",
"severity": "medium",
"rule": "mount_depth",
"test": "(file)",
"line": null,
"problem": "Every test uses full mount, which renders PriorityPicker even though no test is about integration with it.",
"fix": "Use shallowMount so PriorityPicker is stubbed; fire its update:modelValue through findComponent if priority needs testing.",
"code": []
},
{
"id": "F9",
"severity": "low",
"rule": "mock_reset",
"test": "(file)",
"line": 7,
"problem": "The module mock is never cleared between tests, and the pasted component does not import @/analytics at all, so the mock is dead weight.",
"fix":
... (shortened here; the page shows it whole)
The page re-checks the reply; the API does not
The page holds every reply to account (recon.js). Write: three to ten cases covering surface ids the page read; exactly one it() per case, titled with its name, no todo, skip or only; imports from vitest; a beforeEach; the mount and Pinia setup the reply declares actually used in the file; every emit and action covered or named as a gap; each case touching what it claims; and the page's own lint, run on the written file, finding no wrapper.vm, snapshot, unawaited trigger, uncontrolled clock or empty test. Review: every certain lint finding answered under the same rule; no accepted minimal Pinia setup flagged, each one listed; every untested emit and action a coverage gap; the verdict consistent with the severities; cited lines inside the file. Over the API you get the model's reply as it is; run the same checks yourself with Recon.reconcile in recon.js, or paste the same code into the page.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the envelope.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://mountspec.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://mountspec.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// mountspec.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://mountspec.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://mountspec.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// Mountspec.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class Mountspec {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://mountspec.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://mountspec.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://mountspec.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://mountspec.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"mountspec"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "mountspec"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "mountspec" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "mountspec"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "mountspec"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "mountspec" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "mountspec"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "mountspec" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model reads gpt-5.6-terra and model_alias gpt-terra.
# input.json: the run input object - {"task": "write", "title": "...", "source": "...", "facts": "..."}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "write", "title": "...", "source": "...", "facts": "..."}
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task, title, source, facts}
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses mountspec:<lane>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.
KEY="mountspec:write:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the lane's contract
echo "$J" | jq '.data | {charged_credits, truncated}'
import hashlib, time
key = "mountspec:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the lane's JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("headline"))
import { createHash } from "node:crypto";
const key = `mountspec:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.headline);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "mountspec:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
fmt.Println(j["charged_credits"], j["truncated"], j["output"].(map[string]any)["output"])
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "mountspec:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply);
require "digest"
key = "mountspec:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["headline"]].inspect
$key = "mountspec:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["headline"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"mountspec:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply}");
Step 6 — POST /run-stream
The same run as Server-Sent Events. review.json is a review-lane run input, built like input.json.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: mountspec:review:$(shasum -a 256 review.json | cut -c1-16):a1" --data @review.json
rev = json.load(open("review.json")) # the same run input as input.json: {"task": "review", "title": "...", "source": "...", "tests": "...", "facts": "..."}
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "mountspec:review:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("review.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `mountspec:review:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("review.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "mountspec:review:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("review.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "mountspec:review:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("review.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "mountspec:review:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("review.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: mountspec:review:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("review.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"mountspec:review:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);