How Would We Know/API

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"}}.

codemeaning
unauthorizedmissing, expired or wrong-app token
payment_requiredbalance below min_credits
not_foundunknown job id, or a $refs path this release does not declare
rate_limitedback off and retry
validation_errorbody 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.

fieldtypenotes
stagestring, requiredeither plan or execute
requeststring, requiredthe question in plain words
planarrayexecute stage: the confirmed steps, in order, each {id, name}
feasibilityobjectthe browser's sample-size arithmetic; treated as fact by the prompt
droppedarraysteps the user removed, so the model can flag what their absence costs
$refsarray, reservedplatform key, stripped before the model sees the input

1. Get a token

POST /guest

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

GET /me

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

POST /estimate

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

POST /run

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

GET /jobs/JOB_ID

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

POST /run

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

POST /run-stream

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