Base URL and envelope
Base URL https://api.skillsafe.ai/v1/app-api. Every response is
{"ok": true, "data": {...}} or {"ok": false, "error": {"code", "message", "status"}}.
| code | meaning |
|---|---|
| unauthorized | missing, expired or wrong-app token |
| payment_required | balance below min_credits |
| not_found | unknown job id, or a $refs path this release does not declare |
| rate_limited | back off and retry |
| validation_error | body was not a JSON object, or a reserved key was malformed |
The input contract
The request body is the input object. Do not wrap it in an
{"input": {...}} key: that returns 200, prices identically, and hides every field from
the model.
| field | type | notes |
|---|---|---|
stage | string, required | either plan or execute |
request | string, required | the question in plain words |
plan | array | execute stage: the confirmed steps, in order, each {id, name} |
feasibility | object | the browser's sample-size arithmetic; treated as fact by the prompt |
dropped | array | steps the user removed, so the model can flag what their absence costs |
$refs | array, reserved | platform key, stripped before the model sees the input |
1. Get a token
Every call takes an app-user token as Authorization: Bearer aut_.... Open the token page to copy this browser's token, or mint a guest with POST /v1/app-api/guest and {"slug": "how-would-we-know"}. Guests may call /estimate; running the agent needs a signed-in user.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug": "how-would-we-know"}'
import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/guest",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"slug": "how-would-we-know"
},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/guest", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"slug": "how-would-we-know"
})
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{
"slug": "how-would-we-know"
}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/guest", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{
"slug": "how-would-we-know"
}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/guest")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = {
"slug": "how-would-we-know"
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/guest");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"slug": "how-would-we-know"}');
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{
""slug"": ""how-would-we-know""
}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/guest", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());
2. Check who you are
Confirms the token resolves and shows the balance. subject_type is "user" when signed in and "guest" otherwise.
curl -s "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer YOUR_TOKEN"
import requests
TOKEN = "YOUR_TOKEN"
r = requests.get(
"https://api.skillsafe.ai/v1/app-api/me",
headers={"Authorization": f"Bearer {TOKEN}"},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/me", {
method: "GET",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
}
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(``)
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/me", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("GET", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/me")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{}", Encoding.UTF8, "application/json");
var res = await http.GetAsync("https://api.skillsafe.ai/v1/app-api/me");
Console.WriteLine(await res.Content.ReadAsStringAsync());
3. Price the run first
Free, and no job is created. Returns hold_credits (what is reserved), min_credits (the floor below which the run is refused), the concrete model, and input_checked plus warnings from this app's declared input schema. Treat a non-empty warnings as a stop. Note the $refs key: it is what pulls this app's private method catalogue into the run server-side, and it raises the hold because those records arrive after the hold is taken.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"stage": "plan", "request": "we rewrote the signup flow and think it helps but nobody agrees", "$refs": [{"path": "private/skills.jsonl", "q": "hwwk-step", "limit": 40}]}'
import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/estimate",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/estimate", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
})
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/estimate", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/estimate"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/estimate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = {
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/estimate");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"stage": "plan", "request": "we rewrote the signup flow and think it helps but nobody agrees", "$refs": [{"path": "private/skills.jsonl", "q": "hwwk-step", "limit": 40}]}');
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{
""stage"": ""plan"",
""request"": ""we rewrote the signup flow and think it helps but nobody agrees"",
""$refs"": [
{
""path"": ""private/skills.jsonl"",
""q"": ""hwwk-step"",
""limit"": 40
}
]
}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/estimate", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());
4. Propose a plan
The plan stage. Returns 202 {"job_id"}; poll the job for the output. The output is a single JSON object - see the contract below.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"stage": "plan", "request": "we rewrote the signup flow and think it helps but nobody agrees", "$refs": [{"path": "private/skills.jsonl", "q": "hwwk-step", "limit": 40}]}'
import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/run",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
})
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = {
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"stage": "plan", "request": "we rewrote the signup flow and think it helps but nobody agrees", "$refs": [{"path": "private/skills.jsonl", "q": "hwwk-step", "limit": 40}]}');
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{
""stage"": ""plan"",
""request"": ""we rewrote the signup flow and think it helps but nobody agrees"",
""$refs"": [
{
""path"": ""private/skills.jsonl"",
""q"": ""hwwk-step"",
""limit"": 40
}
]
}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());
5. Poll the job
Poll until status is succeeded or failed. price_credits is the hold; charged_credits is what you actually paid. Build any pricing display on charged_credits.
curl -s "https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID" \
-H "Authorization: Bearer YOUR_TOKEN"
import requests
TOKEN = "YOUR_TOKEN"
r = requests.get(
"https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID",
headers={"Authorization": f"Bearer {TOKEN}"},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
}
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(``)
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("GET", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{}", Encoding.UTF8, "application/json");
var res = await http.GetAsync("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID");
Console.WriteLine(await res.Content.ReadAsStringAsync());
6. Run the confirmed plan
The execute stage. Send back the steps the user confirmed, in their confirmed order, and one $refs lookup per step. The output is the line-oriented text format below.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"stage": "execute", "request": "we rewrote the signup flow and think it helps but nobody agrees", "plan": [{"id": "falsifiable-claim", "name": "Turn the belief into a claim that can fail"}, {"id": "ab-experiment", "name": "Design the randomised experiment"}], "$refs": [{"path": "private/playbook.jsonl", "q": "step:falsifiable-claim", "limit": 1}, {"path": "private/playbook.jsonl", "q": "step:ab-experiment", "limit": 1}]}'
import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/run",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"stage": "execute",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"plan": [
{
"id": "falsifiable-claim",
"name": "Turn the belief into a claim that can fail"
},
{
"id": "ab-experiment",
"name": "Design the randomised experiment"
}
],
"$refs": [
{
"path": "private/playbook.jsonl",
"q": "step:falsifiable-claim",
"limit": 1
},
{
"path": "private/playbook.jsonl",
"q": "step:ab-experiment",
"limit": 1
}
]
},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"stage": "execute",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"plan": [
{
"id": "falsifiable-claim",
"name": "Turn the belief into a claim that can fail"
},
{
"id": "ab-experiment",
"name": "Design the randomised experiment"
}
],
"$refs": [
{
"path": "private/playbook.jsonl",
"q": "step:falsifiable-claim",
"limit": 1
},
{
"path": "private/playbook.jsonl",
"q": "step:ab-experiment",
"limit": 1
}
]
})
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{
"stage": "execute",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"plan": [
{
"id": "falsifiable-claim",
"name": "Turn the belief into a claim that can fail"
},
{
"id": "ab-experiment",
"name": "Design the randomised experiment"
}
],
"$refs": [
{
"path": "private/playbook.jsonl",
"q": "step:falsifiable-claim",
"limit": 1
},
{
"path": "private/playbook.jsonl",
"q": "step:ab-experiment",
"limit": 1
}
]
}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{
"stage": "execute",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"plan": [
{
"id": "falsifiable-claim",
"name": "Turn the belief into a claim that can fail"
},
{
"id": "ab-experiment",
"name": "Design the randomised experiment"
}
],
"$refs": [
{
"path": "private/playbook.jsonl",
"q": "step:falsifiable-claim",
"limit": 1
},
{
"path": "private/playbook.jsonl",
"q": "step:ab-experiment",
"limit": 1
}
]
}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = {
"stage": "execute",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"plan": [
{
"id": "falsifiable-claim",
"name": "Turn the belief into a claim that can fail"
},
{
"id": "ab-experiment",
"name": "Design the randomised experiment"
}
],
"$refs": [
{
"path": "private/playbook.jsonl",
"q": "step:falsifiable-claim",
"limit": 1
},
{
"path": "private/playbook.jsonl",
"q": "step:ab-experiment",
"limit": 1
}
]
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"stage": "execute", "request": "we rewrote the signup flow and think it helps but nobody agrees", "plan": [{"id": "falsifiable-claim", "name": "Turn the belief into a claim that can fail"}, {"id": "ab-experiment", "name": "Design the randomised experiment"}], "$refs": [{"path": "private/playbook.jsonl", "q": "step:falsifiable-claim", "limit": 1}, {"path": "private/playbook.jsonl", "q": "step:ab-experiment", "limit": 1}]}');
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{
""stage"": ""execute"",
""request"": ""we rewrote the signup flow and think it helps but nobody agrees"",
""plan"": [
{
""id"": ""falsifiable-claim"",
""name"": ""Turn the belief into a claim that can fail""
},
{
""id"": ""ab-experiment"",
""name"": ""Design the randomised experiment""
}
],
""$refs"": [
{
""path"": ""private/playbook.jsonl"",
""q"": ""step:falsifiable-claim"",
""limit"": 1
},
{
""path"": ""private/playbook.jsonl"",
""q"": ""step:ab-experiment"",
""limit"": 1
}
]
}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());
7. Stream instead of polling
Same contract as /run, delivered as SSE: a job event, then delta events, then done or error.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"stage": "plan", "request": "we rewrote the signup flow and think it helps but nobody agrees", "$refs": [{"path": "private/skills.jsonl", "q": "hwwk-step", "limit": 40}]}'
import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/run-stream",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
},
)
print(r.json())
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run-stream", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
})
});
console.log(await res.json());
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run-stream", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}
import java.net.http.*;
import java.net.URI;
var token = "YOUR_TOKEN";
var body = """
{
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}
""";
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run-stream"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = {
"stage": "plan",
"request": "we rewrote the signup flow and think it helps but nobody agrees",
"$refs": [
{
"path": "private/skills.jsonl",
"q": "hwwk-step",
"limit": 40
}
]
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run-stream");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"stage": "plan", "request": "we rewrote the signup flow and think it helps but nobody agrees", "$refs": [{"path": "private/skills.jsonl", "q": "hwwk-step", "limit": 40}]}');
echo curl_exec($ch);
using System.Net.Http;
using System.Text;
var token = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var content = new StringContent(@"{
""stage"": ""plan"",
""request"": ""we rewrote the signup flow and think it helps but nobody agrees"",
""$refs"": [
{
""path"": ""private/skills.jsonl"",
""q"": ""hwwk-step"",
""limit"": 40
}
]
}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run-stream", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());
Output: the plan stage
A single JSON object. steps is ordered; every id comes
from this app's private catalogue and is stable, so you can key your own UI on it.
{
"stage": "plan",
"reading": "one sentence restating what they are trying to find out",
"recommend": "study",
"confidence": "medium",
"steps": [
{
"id": "ab-experiment",
"name": "Design the randomised experiment",
"why": "one sentence quoting their words",
"stage": "design",
"skill": "@owl-listener/a-b-test-design",
"after": [
"randomisable"
],
"optional": false,
"alternative": {
"id": "quasi-design",
"name": "Design the comparison when you cannot randomise",
"when": "when the old version can no longer be shown"
}
}
],
"unknowns": [
"a fact needed before the plan is safe to run"
],
"excluded": [
{
"id": "diary-study",
"name": "Design the diary study",
"reason": "why it is not needed here"
}
],
"gaps": []
}
Output: the execute stage
Line-oriented on purpose: a truncated response still yields every step that fully
arrived. Parse ## STEP <id> headings, take WHAT: as the summary line,
and everything after the -- separator as that step's body.
HEADLINE: one sentence a decision-maker could act on
VERDICT: run
## STEP falsifiable-claim
WHAT: what this step establishes here
--
the worked content for this step
## STEP ab-experiment
WHAT: ...
--
...
## RISKS
- a risk to this plan and what to do about it
## NEXT
- the first concrete action, with an owner
Notes that will save you a round trip
- The body is the input object. Wrapping it in
{"input": {...}}returns 200, prices identically, and the model sees none of your fields. $refspaths must be declared by the release. An undeclared path is a404 not_foundon/estimateas well as on/run, so estimate is a free way to check your lookups resolve.- A
$refsrun is surcharged by the injection cap, so its hold is larger than the same body without it. Settlement refunds the difference. - At most 8 lookups per run, and injection is capped at 16 KB, truncated at whole-record
boundaries. Use
limit. - Pass an
Idempotency-Keyheader on retries. A replay answers{"job_id", "deduped": true}and nothing else.