← Mountspec / API
Tokens

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

HTTPcodemeaning
400VALIDATION_ERRORThe 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.
401UNAUTHORIZEDNo token, or an expired one: get a new one (step 2).
402INSUFFICIENT_CREDITSThe balance is below the run's minimum. Top up at skillsafe.ai/account/billing/.
403FORBIDDENA guest token tried a metered run: sign in for a personal token.
404NOT_FOUNDUnknown job id.
429RATE_LIMITEDBack off and retry with the same Idempotency-Key.

The task field

Two lanes over the same unit:

Input fields (all strings)

fieldwhencontent
taskrequiredwrite or review
titlerequiredThe unit's name, e.g. TodoForm.
sourcerequiredThe 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.
testsreview onlyThe existing test file, up to 14,000 characters; line 1 is its first line.
focusoptionalWhat to cover or look at, up to 2,000 characters.
factsrequiredA 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_noteoptionalOnly 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"}
}

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')

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}' 

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}' 

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}' 

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