Turn a command's help text into a tldr page from your own pipeline
Send a command name, its --help or man output, and the parsed facts, and get
back one JSON object: the command, 1–2 description_lines,
the more_info_url for the required More information line, and
examples — the 5–8 uses people actually reach for, ordered by
frequency, each a {description, command} pair written to the tldr-pages
placeholder conventions — plus coverage_notes saying what was left out
and see_also for related commands. The output is deterministic to render: the
app's own tldrkit.js turns it into the final Markdown and lints it against
the format rules, and your pipeline can do the same. Wire it into a docs build to draft a
page per CLI tool, batch-document an internal tool suite, or gate merges on the lint's
grounding check. Every code step below is shown in cURL, Python, JavaScript, Go, Java,
Ruby, PHP and C#; pick a language once and the whole page follows.
Basics
Base URL https://api.skillsafe.ai/v1/app-api, app slug
tldr-studio. Every request sends
Authorization: Bearer <token> and JSON bodies with
Content-Type: application/json. Responses are wrapped in an envelope:
{"data": …} on success, {"error": {"code", "message"}}
on failure. Pages are written by the gpt-terra model alias
(currently gpt-5.6-terra) at a publisher markup of
1000 bps — 10%. Credits are in units of 1/10 000 of a US
dollar, so 10 000 credits is $1.00.
POST /guest GET /me POST /estimate POST /run GET /jobs/{id} POST /run-stream POST /collections/pages/query
Error codes
| HTTP | code | What it means and what to do |
|---|---|---|
400 | validation_error | The body is missing a required field or a field has the wrong type. error.details names it. POST /guest in particular needs slug in the body — an X-App-Slug header is not accepted. |
401 | unauthorized | No token, a malformed token, or a token that has expired. Mint a new guest token or sign in again. |
402 | payment_required | The balance cannot cover this run's minimum. Call /estimate first and compare min_credits against /me's credits. |
404 | not_found | Unknown job id, unknown collection, or a record that belongs to another subject. Guest identities are per-token: a new guest token cannot see the previous guest's records. |
409 | conflict | An Idempotency-Key was reused with a different body. Change the attempt counter in the key when the input changes. |
429 | rate_limited | Too many requests. Back off and retry; do not tight-loop. |
500 | internal_error | Transient. Retry with the same Idempotency-Key so the retry cannot bill twice. |
/run and /run-stream.
/guest, /me and /estimate are free, so a client
can price a run, check the balance and prove the model binding without spending
anything.
Step 1 · Get a token
Two ways in. If you already use the app in a browser, open
the token page and press Copy shell export —
it hands you the exact export SKILLSAFE_TOKEN="…" line, with no DevTools
console involved. For a fully scripted client, POST /guest mints a guest
token with no browser at all. Guest tokens can call /me and the free
/estimate; a personal token is what bills page runs to your own account.
# Option A — take the token this browser already has: open /tokens.html,
# press "Copy shell export", and paste the line it gives you.
export SKILLSAFE_TOKEN="aut_xxxxxxxxxxxxxxxxxxxx"
# Option B — mint a guest token with no browser at all. Guest tokens can call
# /me and the free /estimate; sign in for a personal token to bill page runs
# to your own account.
curl -s -X POST https://api.skillsafe.ai/v1/app-api/guest \
-H 'Content-Type: application/json' \
-d '{"slug":"tldr-studio"}'
# => {"data":{"token":"aut_...","subject_type":"guest","credits":0}}
import os, json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
SLUG = "tldr-studio"
def call(path, body=None, token=None, method=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data,
method=method or ("POST" if data else "GET"))
req.add_header("Content-Type", "application/json")
req.add_header("User-Agent", "tldr-studio-client/1.0")
if token:
req.add_header("Authorization", "Bearer " + token)
with urllib.request.urlopen(req) as r:
return json.loads(r.read())["data"]
# Option A: the token from /tokens.html, kept in your environment.
token = os.environ.get("SKILLSAFE_TOKEN")
# Option B: a fresh guest token, no browser involved.
if not token:
token = call("/guest", {"slug": SLUG})["token"]
print(token[:12] + "...")
const BASE = "https://api.skillsafe.ai/v1/app-api";
const SLUG = "tldr-studio";
async function call(path, { body, token, method } = {}) {
const res = await fetch(BASE + path, {
method: method || (body ? "POST" : "GET"),
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: "Bearer " + token } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (json.error) throw Object.assign(new Error(json.error.message), json.error);
return json.data;
}
// Option A: paste the token from /tokens.html (or read it from your own config).
let token = "YOUR_TOKEN";
// Option B: mint a guest token — good for /me and the free /estimate.
if (token === "YOUR_TOKEN") token = (await call("/guest", { body: { slug: SLUG } })).token;
console.log(token.slice(0, 12) + "...");
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
)
const base = "https://api.skillsafe.ai/v1/app-api"
const slug = "tldr-studio"
type envelope struct {
Data json.RawMessage `json:"data"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func call(path, token string, body any, out any) error {
var rdr io.Reader
method := "GET"
if body != nil {
b, _ := json.Marshal(body)
rdr = bytes.NewReader(b)
method = "POST"
}
req, _ := http.NewRequest(method, base+path, rdr)
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var env envelope
if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
return err
}
if env.Error != nil {
return errors.New(env.Error.Code + ": " + env.Error.Message)
}
if out != nil {
return json.Unmarshal(env.Data, out)
}
return nil
}
func main() {
token := os.Getenv("SKILLSAFE_TOKEN")
if token == "" {
var guest struct{ Token string `json:"token"` }
if err := call("/guest", "", map[string]string{"slug": slug}, &guest); err != nil {
panic(err)
}
token = guest.Token
}
fmt.Println(token[:12] + "...")
}
import java.net.URI;
import java.net.http.*;
import java.util.Map;
public class CiteReady {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String SLUG = "tldr-studio";
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String path, String token, String jsonBody) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + path))
.header("Content-Type", "application/json");
if (token != null) b.header("Authorization", "Bearer " + token);
b = jsonBody == null ? b.GET()
: b.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
HttpResponse<String> res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
return res.body(); // {"data":...} or {"error":{...}} — parse with your JSON library
}
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_TOKEN");
if (token == null) {
// POST /guest returns {"data":{"token":"aut_..."}}
System.out.println(call("/guest", null, "{\"slug\":\"" + SLUG + "\"}"));
} else {
System.out.println(token.substring(0, 12) + "...");
}
}
}
require "json"
require "net/http"
BASE = URI("https://api.skillsafe.ai/v1/app-api")
SLUG = "tldr-studio"
def call(path, body: nil, token: nil, method: nil)
uri = URI(BASE.to_s + path)
req = (method || (body ? "POST" : "GET")) == "POST" ?
Net::HTTP::Post.new(uri) : Net::HTTP::Get.new(uri)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{token}" if token
req.body = JSON.generate(body) if body
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
json = JSON.parse(res.body)
raise "#{json['error']['code']}: #{json['error']['message']}" if json["error"]
json["data"]
end
token = ENV["SKILLSAFE_TOKEN"] || call("/guest", body: { slug: SLUG })["token"]
puts token[0, 12] + "..."
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
const SLUG = "tldr-studio";
function call(string $path, ?array $body = null, ?string $token = null): array {
$headers = ["Content-Type: application/json"];
if ($token) { $headers[] = "Authorization: Bearer " . $token; }
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$json = json_decode(curl_exec($ch), true);
curl_close($ch);
if (isset($json["error"])) {
throw new RuntimeException($json["error"]["code"] . ": " . $json["error"]["message"]);
}
return $json["data"];
}
$token = getenv("SKILLSAFE_TOKEN") ?: call("/guest", ["slug" => SLUG])["token"];
echo substr($token, 0, 12) . "...\n";
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
class CiteReady {
const string Base = "https://api.skillsafe.ai/v1/app-api";
const string Slug = "tldr-studio";
static readonly HttpClient Http = new HttpClient();
static async Task<JsonElement> Call(string path, object body = null, string token = null) {
var req = new HttpRequestMessage(body == null ? HttpMethod.Get : HttpMethod.Post, Base + path);
if (token != null) req.Headers.Add("Authorization", "Bearer " + token);
if (body != null) req.Content = JsonContent.Create(body);
var res = await Http.SendAsync(req);
var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (doc.RootElement.TryGetProperty("error", out var err))
throw new Exception(err.GetProperty("code").GetString() + ": " + err.GetProperty("message").GetString());
return doc.RootElement.GetProperty("data");
}
static async Task Main() {
var token = Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN");
if (token == null) {
var guest = await Call("/guest", new { slug = Slug });
token = guest.GetProperty("token").GetString();
}
Console.WriteLine(token.Substring(0, 12) + "...");
}
}
Step 2 · Check who you are and what you can spend
GET /me returns subject_type (user or
guest), subject_id and credits. Compare
credits against /estimate's min_credits
before submitting a run — a 402 after submit is a client bug, not a user
problem.
curl -s https://api.skillsafe.ai/v1/app-api/me \
-H "Authorization: Bearer $SKILLSAFE_TOKEN"
# => {"data":{"subject_type":"user","subject_id":"usr_...","credits":184213}}
#
# subject_type is "user" for a personal token and "guest" for a guest one.
# credits is in credit units: 10 000 credits = $1.00.
me = call("/me", token=token)
print(me["subject_type"], me["credits"], "credits",
"= $%.2f" % (me["credits"] / 10000))
const me = await call("/me", { token });
console.log(me.subject_type, me.credits, "credits =",
"$" + (me.credits / 10000).toFixed(2));
var me struct {
SubjectType string `json:"subject_type"`
SubjectID string `json:"subject_id"`
Credits int64 `json:"credits"`
}
if err := call("/me", token, nil, &me); err != nil {
panic(err)
}
fmt.Printf("%s %d credits = $%.2f\n", me.SubjectType, me.Credits, float64(me.Credits)/10000)
// GET /me — {"data":{"subject_type":"user","credits":184213}}
String me = call("/me", token, null);
System.out.println(me);
me = call("/me", token: token)
puts "#{me['subject_type']} #{me['credits']} credits = $#{'%.2f' % (me['credits'] / 10000.0)}"
$me = call("/me", null, $token);
printf("%s %d credits = $%.2f\n", $me["subject_type"], $me["credits"], $me["credits"] / 10000);
var me = await Call("/me", null, token);
var credits = me.GetProperty("credits").GetInt64();
Console.WriteLine($"{me.GetProperty("subject_type").GetString()} {credits} credits = ${credits / 10000.0:F2}");
Step 3 · Price the run — free, and it proves the model binding
POST /estimate takes the same body as /run, creates no job
and charges nothing. It returns model, model_alias,
markup_bps, hold_credits, min_credits and
sponsor_enabled. Present hold_credits as reserved,
never as the price: the hold covers the full output cap, and the settled
charged_credits is usually far lower.
# /estimate is free: no job is created, no credits are held, nothing is charged.
# Use it to show a price and to prove the model binding before you spend anything.
curl -s -X POST https://api.skillsafe.ai/v1/app-api/estimate \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H 'Content-Type: application/json' \
-d @page-input.json
# => {"data":{"model":"gpt-5.6-terra","model_alias":"gpt-terra","markup_bps":1000,
# "hold_credits":3120,"min_credits":260,"sponsor_enabled":false}}
est = call("/estimate", body=page_input, token=token)
print("model", est["model"], "alias", est["model_alias"], "markup", est["markup_bps"])
print("reserved up to $%.4f" % (est["hold_credits"] / 10000))
if me["credits"] < est["min_credits"]:
raise SystemExit("balance below the model minimum — top up before running")
const est = await call("/estimate", { body: pageInput, token });
console.log(est.model, est.model_alias, est.markup_bps);
console.log("reserved up to $" + (est.hold_credits / 10000).toFixed(4));
if (me.credits < est.min_credits) throw new Error("balance below the model minimum");
var est struct {
Model string `json:"model"`
ModelAlias string `json:"model_alias"`
MarkupBps int `json:"markup_bps"`
HoldCredits int64 `json:"hold_credits"`
MinCredits int64 `json:"min_credits"`
}
if err := call("/estimate", token, pageInput, &est); err != nil {
panic(err)
}
fmt.Printf("%s (%s) markup %d bps, reserve $%.4f\n",
est.Model, est.ModelAlias, est.MarkupBps, float64(est.HoldCredits)/10000)
// POST /estimate with the same body you would send to /run. Free, no job.
String est = call("/estimate", token, pageInputJson);
System.out.println(est);
est = call("/estimate", body: page_input, token: token)
puts "#{est['model']} (#{est['model_alias']}) markup #{est['markup_bps']} bps"
puts "reserved up to $#{'%.4f' % (est['hold_credits'] / 10000.0)}"
$est = call("/estimate", $page_input, $token);
printf("%s (%s) markup %d bps, reserve $%.4f\n",
$est["model"], $est["model_alias"], $est["markup_bps"], $est["hold_credits"] / 10000);
var est = await Call("/estimate", pageInput, token);
Console.WriteLine(est.GetProperty("model").GetString() + " / " +
est.GetProperty("model_alias").GetString() + " markup " +
est.GetProperty("markup_bps").GetInt32() + " bps");
Step 4 · Run the writing pass and poll for it
POST /run returns {"job_id"}; poll
GET /jobs/{id} until status is succeeded or
failed, then read data.output.output — the page as a JSON
string. Always send Idempotency-Key, derived from the input
plus an attempt counter: a network blip or a retry after a malformed reply must never
bill the same page twice. Reuse the key for a retry of the same input; bump the
attempt counter only when the input itself changes.
# Metered. Always send Idempotency-Key: a retry with the same key returns the
# same job instead of billing twice.
KEY="tldr-studio:$(shasum -a 256 page-input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST https://api.skillsafe.ai/v1/app-api/run \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $KEY" \
-d @page-input.json | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["job_id"])')
# Poll until terminal.
while true; do
OUT=$(curl -s "https://api.skillsafe.ai/v1/app-api/jobs/$JOB" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN")
STATUS=$(printf '%s' "$OUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["status"])')
[ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] && break
sleep 2
done
printf '%s' "$OUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["output"]["output"])'
# => the page, as one JSON object (see the output contract below).
import hashlib, time
def idem_key(inp, attempt=1):
seed = "\u0020".join(str(inp.get(k, "")) for k in
("command", "help_text"))
return "tldr-studio:%s:a%d" % (hashlib.sha256(seed.encode()).hexdigest()[:16], attempt)
def run_page(inp, token, attempt=1):
data = json.dumps(inp).encode()
req = urllib.request.Request(BASE + "/run", data=data, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer " + token)
req.add_header("Idempotency-Key", idem_key(inp, attempt))
with urllib.request.urlopen(req) as r:
job_id = json.loads(r.read())["data"]["job_id"]
while True:
job = call("/jobs/" + job_id, token=token)
if job["status"] in ("succeeded", "failed"):
break
time.sleep(2)
if job["status"] == "failed":
raise RuntimeError(job.get("error") or "run failed")
return json.loads(job["output"]["output"])
page = run_page(page_input, token)
print(page["command"], "-", len(page["examples"]), "examples")
import { createHash } from "node:crypto";
function idemKey(inp, attempt = 1) {
const seed = ["command", "help_text"]
.map((k) => String(inp[k] ?? "")).join(" ");
return `tldr-studio:${createHash("sha256").update(seed).digest("hex").slice(0, 16)}:a${attempt}`;
}
async function runPage(inp, token, attempt = 1) {
const res = await fetch(BASE + "/run", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
"Idempotency-Key": idemKey(inp, attempt),
},
body: JSON.stringify(inp),
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
let job;
do {
await new Promise((r) => setTimeout(r, 2000));
job = await call("/jobs/" + data.job_id, { token });
} while (job.status !== "succeeded" && job.status !== "failed");
if (job.status === "failed") throw new Error(job.error || "run failed");
return JSON.parse(job.output.output);
}
const page = await runPage(pageInput, token);
console.log(page.command, "-", page.examples.length + " examples");
import (
"crypto/sha256"
"encoding/hex"
"strings"
"time"
)
func idemKey(inp map[string]any, attempt int) string {
parts := []string{}
for _, k := range []string{"command", "help_text"} {
parts = append(parts, fmt.Sprint(inp[k]))
}
sum := sha256.Sum256([]byte(strings.Join(parts, " ")))
return fmt.Sprintf("tldr-studio:%s:a%d", hex.EncodeToString(sum[:])[:16], attempt)
}
// POST /run with the Idempotency-Key header, then poll GET /jobs/{id} every two
// seconds until status is "succeeded" or "failed". job.Output.Output holds the
// page as a JSON string; unmarshal it into your own struct.
func runPage(inp map[string]any, token string) (string, error) {
b, _ := json.Marshal(inp)
req, _ := http.NewRequest("POST", base+"/run", bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", idemKey(inp, 1))
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
var env envelope
json.NewDecoder(res.Body).Decode(&env)
var started struct{ JobID string `json:"job_id"` }
json.Unmarshal(env.Data, &started)
for {
var job struct {
Status string `json:"status"`
Output struct{ Output string `json:"output"` } `json:"output"`
}
if err := call("/jobs/"+started.JobID, token, nil, &job); err != nil {
return "", err
}
if job.Status == "succeeded" {
return job.Output.Output, nil
}
if job.Status == "failed" {
return "", errors.New("run failed")
}
time.Sleep(2 * time.Second)
}
}
// POST /run must carry Idempotency-Key, derived from the input plus an attempt
// counter, so a network retry cannot bill the page twice.
String key = "tldr-studio:" + sha256Hex(command + helpText).substring(0, 16) + ":a1";
HttpRequest run = HttpRequest.newBuilder(URI.create(BASE + "/run"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(pageInputJson))
.build();
String started = HTTP.send(run, HttpResponse.BodyHandlers.ofString()).body();
// started => {"data":{"job_id":"job_..."}}
// then poll GET /jobs/{job_id} until status is succeeded or failed, and read
// data.output.output — the page JSON as a string.
require "digest"
def idem_key(inp, attempt = 1)
seed = %w[command help_text].map { |k| inp[k].to_s }.join(" ")
"tldr-studio:#{Digest::SHA256.hexdigest(seed)[0, 16]}:a#{attempt}"
end
def run_page(inp, token)
uri = URI(BASE.to_s + "/run")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{token}"
req["Idempotency-Key"] = idem_key(inp)
req.body = JSON.generate(inp)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
job_id = JSON.parse(res.body)["data"]["job_id"]
loop do
job = call("/jobs/#{job_id}", token: token)
return JSON.parse(job["output"]["output"]) if job["status"] == "succeeded"
raise "run failed" if job["status"] == "failed"
sleep 2
end
end
page = run_page(page_input, token)
puts "#{page['command']} - #{page['examples'].length} examples"
function idem_key(array $inp, int $attempt = 1): string {
$seed = implode(" ", array_map(fn($k) => (string)($inp[$k] ?? ""),
["command", "help_text"]));
return "tldr-studio:" . substr(hash("sha256", $seed), 0, 16) . ":a" . $attempt;
}
function run_page(array $inp, string $token): array {
$ch = curl_init(BASE . "/run");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($inp),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer " . $token,
"Idempotency-Key: " . idem_key($inp),
],
]);
$job_id = json_decode(curl_exec($ch), true)["data"]["job_id"];
curl_close($ch);
while (true) {
$job = call("/jobs/" . $job_id, null, $token);
if ($job["status"] === "succeeded") { return json_decode($job["output"]["output"], true); }
if ($job["status"] === "failed") { throw new RuntimeException("run failed"); }
sleep(2);
}
}
$page = run_page($page_input, $token);
echo $page["command"] . " - " . count($page["examples"]) . " examples" . "\n";
using System.Security.Cryptography;
using System.Text;
static string IdemKey(Dictionary<string, object> inp, int attempt = 1) {
var seed = string.Join(" ", new[] { "command", "help_text" }
.Select(k => inp.TryGetValue(k, out var v) ? v?.ToString() ?? "" : ""));
var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(seed))).ToLowerInvariant();
return $"tldr-studio:{hash[..16]}:a{attempt}";
}
var req = new HttpRequestMessage(HttpMethod.Post, Base + "/run") {
Content = JsonContent.Create(pageInput)
};
req.Headers.Add("Authorization", "Bearer " + token);
req.Headers.Add("Idempotency-Key", IdemKey(pageInput));
var started = JsonDocument.Parse(await (await Http.SendAsync(req)).Content.ReadAsStringAsync());
var jobId = started.RootElement.GetProperty("data").GetProperty("job_id").GetString();
// Poll GET /jobs/{jobId} every two seconds; on "succeeded", data.output.output is
// the page as a JSON string.
Step 5 · Or stream it
POST /run-stream is the same call over server-sent events, which is what
the web app uses so it can show progress. The frame name arrives on the
event: line — job, delta, done — and
is not a type field inside the payload. Concatenate every
delta payload's text to rebuild the JSON, and read
charged_credits and truncated from the done frame.
If truncated is true the output cap was reduced to fit the balance: render
what parsed and tell the user, rather than presenting a clipped plan as complete.
# Server-sent events. Frame names arrive on the `event:` line, not as a field in
# the payload — `delta` carries text chunks, `job` the job id, `done` the
# settlement (charged_credits, truncated).
curl -N -X POST https://api.skillsafe.ai/v1/app-api/run-stream \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $KEY" \
-d @page-input.json
# event: job
# data: {"job_id":"job_..."}
# event: delta
# data: {"text":"{\"command\":\"shipctl\", \"platf"}
# ...
# event: done
# data: {"status":"succeeded","charged_credits":812,"truncated":false}
def run_stream(inp, token, attempt=1, on_delta=None):
data = json.dumps(inp).encode()
req = urllib.request.Request(BASE + "/run-stream", data=data, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer " + token)
req.add_header("Idempotency-Key", idem_key(inp, attempt))
raw, event = "", None
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().rstrip("\n")
if line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
payload = json.loads(line[5:].strip() or "{}")
if event == "delta":
raw += payload.get("text", "")
if on_delta:
on_delta(payload.get("text", ""))
elif event == "done":
return json.loads(raw), payload
raise RuntimeError("stream ended without a done frame")
page, settle = run_stream(page_input, token)
print(page["command"], "charged", settle["charged_credits"])
async function runStream(inp, token, onDelta, attempt = 1) {
const res = await fetch(BASE + "/run-stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
"Idempotency-Key": idemKey(inp, attempt),
},
body: JSON.stringify(inp),
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "", raw = "", event = null;
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
const lines = buf.split("\n");
buf = lines.pop();
for (const line of lines) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) {
const payload = JSON.parse(line.slice(5).trim() || "{}");
if (event === "delta") { raw += payload.text || ""; onDelta?.(payload.text || ""); }
else if (event === "done") return { page: JSON.parse(raw), settle: payload };
}
}
}
throw new Error("stream ended without a done frame");
}
const { page, settle } = await runStream(pageInput, token, (t) => process.stdout.write(t));
console.log("\n", page.command, "charged", settle.charged_credits);
// POST /run-stream and read the SSE frames. The frame name is on the `event:`
// line; `delta` payloads carry {"text":"..."} and concatenate into the page JSON.
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", idemKey(inp, 1))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 1<<20), 1<<20)
var raw strings.Builder
event := ""
for sc.Scan() {
line := sc.Text()
switch {
case strings.HasPrefix(line, "event:"):
event = strings.TrimSpace(line[6:])
case strings.HasPrefix(line, "data:"):
payload := strings.TrimSpace(line[5:])
if event == "delta" {
var d struct{ Text string `json:"text"` }
json.Unmarshal([]byte(payload), &d)
raw.WriteString(d.Text)
} else if event == "done" {
fmt.Println("settled:", payload)
fmt.Println("page:", raw.String())
return
}
}
}
// POST /run-stream with BodyHandlers.ofLines() and fold the SSE frames yourself.
HttpRequest stream = HttpRequest.newBuilder(URI.create(BASE + "/run-stream"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(pageInputJson))
.build();
StringBuilder raw = new StringBuilder();
String[] event = { "" };
HTTP.send(stream, HttpResponse.BodyHandlers.ofLines()).body().forEach(line -> {
if (line.startsWith("event:")) {
event[0] = line.substring(6).trim();
} else if (line.startsWith("data:") && event[0].equals("delta")) {
// parse {"text":"..."} with your JSON library and append it
raw.append(extractText(line.substring(5).trim()));
}
});
System.out.println(raw); // the page JSON
def run_stream(inp, token, attempt = 1)
uri = URI(BASE.to_s + "/run-stream")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{token}"
req["Idempotency-Key"] = idem_key(inp, attempt)
req.body = JSON.generate(inp)
raw = ""
event = nil
settle = nil
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
res.read_body do |chunk|
chunk.each_line do |line|
line = line.chomp
if line.start_with?("event:")
event = line[6..].strip
elsif line.start_with?("data:")
payload = JSON.parse(line[5..].strip.empty? ? "{}" : line[5..].strip)
raw << payload.fetch("text", "") if event == "delta"
settle = payload if event == "done"
end
end
end
end
end
[JSON.parse(raw), settle]
end
page, settle = run_stream(page_input, token)
puts "#{page['command']} charged #{settle['charged_credits']}"
// POST /run-stream with a write callback; the frame name arrives on `event:`.
$raw = "";
$event = "";
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($page_input),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer " . $token,
"Idempotency-Key: " . idem_key($page_input),
],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$raw, &$event) {
foreach (explode("\n", $chunk) as $line) {
$line = rtrim($line);
if (str_starts_with($line, "event:")) {
$event = trim(substr($line, 6));
} elseif (str_starts_with($line, "data:") && $event === "delta") {
$payload = json_decode(trim(substr($line, 5)), true) ?: [];
$raw .= $payload["text"] ?? "";
}
}
return strlen($chunk);
},
]);
curl_exec($ch);
curl_close($ch);
$page = json_decode($raw, true);
echo $page["command"] . "\n";
var sreq = new HttpRequestMessage(HttpMethod.Post, Base + "/run-stream") {
Content = JsonContent.Create(pageInput)
};
sreq.Headers.Add("Authorization", "Bearer " + token);
sreq.Headers.Add("Idempotency-Key", IdemKey(pageInput));
using var sres = await Http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await sres.Content.ReadAsStreamAsync());
var raw = new StringBuilder();
string? evt = null, line;
while ((line = await reader.ReadLineAsync()) != null) {
if (line.StartsWith("event:")) {
evt = line[6..].Trim();
} else if (line.StartsWith("data:")) {
var payload = JsonDocument.Parse(line[5..].Trim() is { Length: > 0 } s ? s : "{}");
if (evt == "delta" && payload.RootElement.TryGetProperty("text", out var t))
raw.Append(t.GetString());
else if (evt == "done")
Console.WriteLine("settled: " + payload.RootElement);
}
}
Console.WriteLine(raw.ToString());
Step 6 · Read the page history — and search it by meaning
Past pages are stored in a declared collection named pages, with
command, description, platform,
example_count, lint_fails and ran_at as indexed
fields, and command and description as the embedded
(vector-searchable) ones. Every where entry must be an operator object
({"eq": …}); a bare value is rejected. Operators:
eq ne lt lte gt gte in contains. Records are scoped to the calling subject,
and each POST /guest mints a new guest identity, so reuse one token
across writes and reads. The full model result, the final page Markdown and a capped copy
of the help text ride along as undeclared keys — stored and returned intact, just
not filterable.
POST /collections/pages/query, but the record CRUD paths sit under
/records and wrap the document in a doc envelope:
POST /collections/pages/records with
{"doc": {…}} → {"data":{"record":{"record_id":"rec_…"}}}
GET /collections/pages/records/{record_id} ·
PUT /collections/pages/records/{record_id} ·
DELETE /collections/pages/records/{record_id}
Semantic search is
POST /collections/pages/similar with
{"text": "the container tool that copies files", "limit": 8} — each hit
carries a cosine score. It is rate-limited to 30 requests/minute per IP and
costs roughly ten times a filtered query, so debounce it and prefer where
whenever an exact match would do. Indexing is asynchronous and only records written
after the collection was declared are searchable.
# Filtered query: lint-clean pages for the common platform, newest first.
curl -s -X POST https://api.skillsafe.ai/v1/app-api/collections/pages/query \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"where":{"platform":{"eq":"common"},"lint_fails":{"eq":0}},
"sort":{"field":"ran_at","dir":"desc"},"limit":20}'
# Semantic search over command + description (30/min per IP; ~10x a query):
curl -s -X POST https://api.skillsafe.ai/v1/app-api/collections/pages/similar \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"text":"the container tool that copies files","limit":8}'
res = call("/collections/pages/query", body={
"where": {"example_count": {"gte": 5}},
"sort": {"field": "ran_at", "dir": "desc"},
"limit": 24,
}, token=token)
for rec in res["records"]:
d = rec["doc"]
print(d["ran_at"], d["command"], "-", d["example_count"], "examples -", d["platform"])
hits = call("/collections/pages/similar",
body={"text": "the container tool that copies files", "limit": 8},
token=token)
for rec in hits["records"]:
print("%.2f" % rec.get("score", 0), rec["doc"]["command"])
const res = await call("/collections/pages/query", {
token,
body: {
where: { example_count: { gte: 5 } },
sort: { field: "ran_at", dir: "desc" },
limit: 24,
},
});
for (const rec of res.records) {
const d = rec.doc;
console.log(d.ran_at, d.command, "-", d.example_count, "examples -", d.platform);
}
const hits = await call("/collections/pages/similar", {
token,
body: { text: "the container tool that copies files", limit: 8 },
});
for (const rec of hits.records) console.log(rec.score, rec.doc.command);
// POST /collections/pages/query with an operator object per where field.
query := map[string]any{
"where": map[string]any{"example_count": map[string]any{"gte": 5}},
"sort": map[string]string{"field": "ran_at", "dir": "desc"},
"limit": 24,
}
var res struct {
Records []struct {
RecordID string `json:"record_id"`
Doc map[string]any `json:"doc"`
} `json:"records"`
}
if err := call("/collections/pages/query", token, query, &res); err != nil {
panic(err)
}
for _, r := range res.Records {
fmt.Println(r.Doc["ran_at"], r.Doc["command"], r.Doc["example_count"])
}
// POST /collections/pages/query
String q = "{\"where\":{\"example_count\":{\"gte\":5}}," +
"\"sort\":{\"field\":\"ran_at\",\"dir\":\"desc\"},\"limit\":24}";
System.out.println(call("/collections/pages/query", token, q));
// Semantic search: POST /collections/pages/similar {"text":"...","limit":8}
res = call("/collections/pages/query", body: {
"where" => { "example_count" => { "gte" => 5 } },
"sort" => { "field" => "ran_at", "dir" => "desc" },
"limit" => 24,
}, token: token)
res["records"].each do |rec|
d = rec["doc"]
puts "#{d['ran_at']} #{d['command']} - #{d['example_count']} examples - #{d['platform']}"
end
$res = call("/collections/pages/query", [
"where" => ["example_count" => ["gte" => 5]],
"sort" => ["field" => "ran_at", "dir" => "desc"],
"limit" => 24,
], $token);
foreach ($res["records"] as $rec) {
$d = $rec["doc"];
echo "{$d['ran_at']} {$d['command']} - {$d['example_count']} examples\n";
}
var q = new {
where = new { example_count = new { gte = 5 } },
sort = new { field = "ran_at", dir = "desc" },
limit = 24
};
var res = await Call("/collections/pages/query", q, token);
foreach (var rec in res.GetProperty("records").EnumerateArray()) {
var d = rec.GetProperty("doc");
Console.WriteLine($"{d.GetProperty("ran_at")} {d.GetProperty("command")}");
}
The input schema
These are the exact fields the app submits. The help text is parsed locally
before the run: every option, subcommand and URL it documents becomes a measured fact in
prescan, and that is what the reply is held to — a flag used in an
example that appears nowhere in the help text is printed by name next to the rendered
page. Long help text is clipped head-and-tail for the wire (the app keeps the first 16k
and last 4k characters with a cut marker), but prescan is computed from the
full text, so nothing the parser saw is lost. A client that computes no prescan may send
an empty object; the page still gets written, it simply has nothing to be reconciled
against.
| Field | Type | Meaning |
|---|---|---|
command | string | The page title, lowercase; subcommand pages join with a hyphen (git-commit). |
platform | string | One of common linux osx windows android freebsd netbsd openbsd sunos — the tldr-pages folder the page targets. |
doc_url | string | The authoritative documentation URL, possibly empty. When given it becomes more_info_url verbatim. |
notes | string | Optional guidance: audience, emphasis, examples the user insists on. |
help_text | string | The pasted --help / man / documentation text — the hard boundary on claims: no flag outside it may appear in an example. |
current_page | string | Optional: an existing page draft to improve. prescan.draft_lint carries what the lint found wrong with it. |
prescan.options | array | {short, long, arg, desc} per documented option, parsed from the full help text (first 60 sent). |
prescan.subcommands | array | {name, desc} per documented subcommand (first 40 sent). |
prescan.urls | array | Every URL found in the help text — the fallback source for more_info_url. |
prescan.option_count, subcommand_count | number | Totals before the send caps. |
prescan.help_chars_total, help_chars_sent | number | How much help text exists and how much of it travelled — honest clipping, declared. |
prescan.draft_lint | array | {level, id, label} per format rule the lint measured on current_page; empty with no draft. |
retry_note | string | Send only when re-asking after a malformed reply, with the same idempotency key seed and a bumped attempt counter. |
A complete body
{
"command": "imgpress",
"platform": "common",
"doc_url": "",
"notes": "This is for a README aimed at photographers, not developers.",
"help_text": "imgpress 3.2.0\nLossless-first image compressor for PNG, JPEG and WebP.\n\nUsage: imgpress [options] <files...>\n\nOptions:\n -o, --output <dir> Write compressed files to this directory\n -q, --quality <1-100> Lossy quality; omit for lossless mode\n -w, --webp Also emit a .webp next to each output file\n -r, --recursive Descend into directories given as arguments\n -h, --help Show this help\n\nReport bugs at https://imgpress.example.org/issues\nManual: https://imgpress.example.org/manual",
"current_page": "",
"prescan": {
"options": [
{ "short": "-o", "long": "--output", "arg": "dir", "desc": "Write compressed files to this directory" },
{ "short": "-q", "long": "--quality", "arg": "1-100", "desc": "Lossy quality; omit for lossless mode" },
{ "short": "-w", "long": "--webp", "arg": "", "desc": "Also emit a .webp next to each output file" },
{ "short": "-r", "long": "--recursive", "arg": "", "desc": "Descend into directories given as arguments" },
{ "short": "-h", "long": "--help", "arg": "", "desc": "Show this help" }
],
"subcommands": [],
"urls": ["https://imgpress.example.org/issues", "https://imgpress.example.org/manual"],
"option_count": 5,
"subcommand_count": 0,
"help_chars_total": 515,
"help_chars_sent": 515,
"draft_lint": []
}
}
The output contract
The reply is one JSON object and nothing else. Parse defensively
anyway: strip a stray code fence, take the span from the first { to the
last }, and re-ask once with a retry_note and the same
idempotency seed if it does not parse. These are the fields the app's own render
path requires, and the constraints it enforces.
| Field | Constraint |
|---|---|
command | Non-empty, lowercased by the app; spaces become hyphens. The page title and the export file name. |
platform | One of the nine platform folders; anything else falls back to the platform that was sent. |
description_lines | Non-empty array of 1–2 strings, each a short sentence ending with a period; extras beyond two are dropped with a warning. The second line is the place for the “Some subcommands such as … have their own usage documentation.” template. |
more_info_url | An http(s) URL or empty. Anything else is dropped with a warning. The renderer wraps it as More information: <url>. — the line the tldr format requires. |
examples | 1–8 entries, each with non-empty description and command — an empty array or a missing field is a hard rejection and triggers the one retry. Descriptions arrive capitalised with no trailing colon (the renderer adds it); commands arrive without backticks (the renderer wraps them). More than 8 are cut with a warning. |
coverage_notes | Array, may be empty. When the help text cannot support five examples, this is where the reply says so — the honest-coverage lane. |
see_also | Array, may be empty. Related commands from the same suite worth their own page. |
TldrKit.lint's grounding rule) and prints every unvouched
flag next to the rendered page. And format is measured, not asserted:
the same lint that judges a hand-written page judges the model's — title shape,
description block, the More information line, example count and shape, the
{{[-s|--long]}} placeholder conventions. If you build your own client, run
the lint before you publish; the module is a static file away.
The free lane is client-side, and you can have it too
The engine ships with the app as tldrkit.js and
calls no network: the help-text parser (options with short/long forms and arguments,
subcommand tables, usage lines, URLs, man-page headers), the skeleton-page generator,
the tolerant page parser, and the format lint — title, description block, More
information line, example count and shape, placeholder conventions, whitespace hygiene,
and the grounding check against parsed help facts. It exposes
window.TldrKit.parseHelp(text), skeleton(facts, opts),
parsePage(md), lint(md, facts), toMarkdown(result),
flagsUsed(cmd) and summarizeLint(entries). A pipeline can lint
every page the model returns — or lint pages it wrote itself — without
spending anything.
window stub
(global.window = {}); parsing and lint are pure string work with no I/O.
The same input always parses to the same facts and the same page always lints to the
same findings, so a CI job can gate a docs repo on lint() reporting zero
fails — including the grounding rule, by handing it the tool's real
--help output.