Driving Book Summary from your own code
Book Summary is a SkillSafe app, so it is reachable through the generic app API. Base URL:
https://api.skillsafe.ai/v1/app-api
The app is identified by the token you call with, not by a header. There is no
X-App-Slug: a token minted for book-summary already scopes every call to
this app.
The response envelope
Every endpoint returns the same two-key envelope. Exactly one of the keys is present.
{"ok": true, "data": { ... }}
{"ok": false, "error": {"code": "INSUFFICIENT_CREDITS", "message": "..."}}
| Code | HTTP | What it means |
|---|---|---|
UNAUTHORIZED | 401 | Missing, malformed or expired token. |
FORBIDDEN | 403 | A guest token tried a signed-in-only call. /run, /run-stream and /search all require a personal token. |
INSUFFICIENT_CREDITS | 402 | Balance below min_credits. Call /estimate first and you will never see this. |
VALIDATION_ERROR | 400 | The input object is the wrong shape, or a search provider was requested that this app has not declared. |
RATE_LIMITED | 429 | Too many calls, or this app has spent its daily upstream search quota. |
NOT_FOUND | 404 | Unknown job id or record id. |
What this app takes and returns
These fields are taken from the running app.js, not from intent. Only
title is required; everything else changes the brief rather than gating it.
| Field | Type | Notes |
|---|---|---|
title | string | Required. Up to 300 characters. |
author | string | Optional but load-bearing: it is what makes the grounding search land on one article rather than a disambiguation page. |
form | enum | novel, non-fiction, poetry, song, play, short-story, memoir, essays, reference, graphic-novel, unknown. Setting poetry, song or short-story forbids quotation entirely. |
reader | string | Who is asking and why. Changes what the brief leads with more than any other field. |
ask | string | What you want from it. Up to 1200 characters; the browser clips longer input from the middle and marks the cut. |
spoilers | enum | avoid or ending-ok. |
depth | enum | quick, standard, thorough. Scales structure, themes and the reading list. It never scales the quotation budget. |
sources | array | Retrieved records, each {id, title, abstract, url} with id of the form SRC-1. See step 3. |
page_facts | object | What the caller worked out for itself: records_retrieved, form_read_as, form_read_from, short_form. |
ask_flags | array | Reproduction requests the caller's own guard spotted, each {matched, why, instead}. The model must address these in declined. |
follow_up | object | {of, previous, goal} for a second pass that holds the facts steady. |
What it will refuse
This app takes a copyrighted work as input, and the line it holds is enforced on the server as well as in the browser — a client-only guard is decorative when the API is public. Summary, synopsis, thematic analysis, structural breakdown, context, comparison and reading guidance are all in scope and are produced generously, including when the stated reason is that you do not want to read the book. That is a normal request.
What you will not get back is the work's text: no extended quotation, no reproduced passage,
page, chapter, stanza or lyric, and no chapter-by-chapter paraphrase dense enough to substitute
for reading. Brief quotation carrying a critical point is allowed within a fixed budget —
3 extracts, 25 words each, 50 words total, each attributed and each with a stated purpose
— and for poetry, songs and short stories the budget is zero. Asking anyway produces a
declined entry and the in-scope version of the request, not an error.
1. Get a token
Easiest route: open the token page in a browser, sign in, and press
Copy shell export. Or mint a guest token, which is enough for /me and
/estimate but not for /search or /run.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"slug\": \"book-summary\"}"import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/guest",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"slug": "book-summary"
},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])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": "book-summary"
})
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
payload := `{"slug": "book-summary"}`
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/guest", bytes.NewBufferString(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String payload = """
{
"slug": "book-summary"
}""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
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": "book-summary"}.to_s
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$payload = '{"slug": "book-summary"}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/guest",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $payload,
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var payload = new StringContent(@"{""slug"": ""book-summary""}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/guest", payload);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
2. Check who you are
/me returns exactly three fields: subject_type,
subject_id and credits. There is no email or name to key off —
the signed-in test is subject_type === "user".
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN"import requests
TOKEN = "YOUR_TOKEN"
r = requests.get(
"https://api.skillsafe.ai/v1/app-api/me",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])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"
}
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/me", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
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 JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/me",
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var res = await http.GetAsync("https://api.skillsafe.ai/v1/app-api/me");
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
3. Ground the book — the step that makes this app worth calling
Skip this and you still get a brief, but every fact in it is unchecked model recall, which is
precisely where invented books, wrong authors and non-existent editions come from. This app
declares one search provider, web.wikipedia. Signed-in callers only.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/search" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"provider\": \"web.wikipedia\", \"query\": \"Things Fall Apart Chinua Achebe\", \"limit\": 8}"import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/search",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"provider": "web.wikipedia",
"query": "Things Fall Apart Chinua Achebe",
"limit": 8
},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/search", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"provider": "web.wikipedia",
"query": "Things Fall Apart Chinua Achebe",
"limit": 8
})
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
payload := `{"provider": "web.wikipedia", "query": "Things Fall Apart Chinua Achebe", "limit": 8}`
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/search", bytes.NewBufferString(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String payload = """
{
"provider": "web.wikipedia",
"query": "Things Fall Apart Chinua Achebe",
"limit": 8
}""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/search"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/search")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = {"provider": "web.wikipedia", "query": "Things Fall Apart Chinua Achebe", "limit": 8}.to_s
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$payload = '{"provider": "web.wikipedia", "query": "Things Fall Apart Chinua Achebe", "limit": 8}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/search",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $payload,
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var payload = new StringContent(@"{""provider"": ""web.wikipedia"", ""query"": ""Things Fall Apart Chinua Achebe"", ""limit"": 8}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/search", payload);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Records come back as {record_id, title, abstract, url} plus a top-level
retrieved_at. Re-label them SRC-1, SRC-2… in the
order you send them, and pass them as sources.
The part that is easy to get wrong. These are MediaWiki snippets,
not article infoboxes. A snippet about the right book very often does not mention the year,
the language, or the author's full name. The brief is required to cite a source only when
that source's own words carry the fact, and to write model-knowledge otherwise.
If you are building your own verification on top of this, check that the cited abstract contains the claimed value — not merely that some record id was cited. Confirming only that a citation exists is satisfied by citing a real page for a claim that page never makes, which manufactures the appearance of rigour around a confabulation. That failure is worse than an uncited claim, not better, and should be surfaced more loudly.
4. Price the run — free, no job created
/estimate costs nothing and creates nothing. It returns model,
model_alias, markup_bps, hold_credits,
min_credits and sponsor_enabled. Compare hold_credits
against the balance from /me before you submit and a 402 becomes impossible.
hold_credits is what is reserved, not what you pay: it prices the full
output cap. The actual charge comes back as charged_credits and is usually far
lower.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\": \"Things Fall Apart\", \"author\": \"Chinua Achebe\", \"form\": \"unknown\", \"reader\": \"a book group meeting on Thursday\", \"ask\": \"the major themes and how the book is structured\", \"spoilers\": \"avoid\", \"depth\": \"standard\", \"sources\": [{\"id\": \"SRC-1\", \"title\": \"Things Fall Apart\", \"abstract\": \"Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.\", \"url\": \"https://en.wikipedia.org/wiki/Things_Fall_Apart\"}], \"page_facts\": {\"records_retrieved\": 1, \"form_read_as\": \"novel\", \"form_read_from\": \"the retrieved abstract\", \"short_form\": false}, \"ask_flags\": []}"import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/estimate",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])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({
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
})
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
payload := `{"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}`
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/estimate", bytes.NewBufferString(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String payload = """
{
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
}""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/estimate"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
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 = {"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}.to_s
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$payload = '{"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/estimate",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $payload,
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var payload = new StringContent(@"{""title"": ""Things Fall Apart"", ""author"": ""Chinua Achebe"", ""form"": ""unknown"", ""reader"": ""a book group meeting on Thursday"", ""ask"": ""the major themes and how the book is structured"", ""spoilers"": ""avoid"", ""depth"": ""standard"", ""sources"": [{""id"": ""SRC-1"", ""title"": ""Things Fall Apart"", ""abstract"": ""Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958."", ""url"": ""https://en.wikipedia.org/wiki/Things_Fall_Apart""}], ""page_facts"": {""records_retrieved"": 1, ""form_read_as"": ""novel"", ""form_read_from"": ""the retrieved abstract"", ""short_form"": false}, ""ask_flags"": []}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/estimate", payload);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
5. Run it and poll
Pass an Idempotency-Key header on every run. A retried request carrying the same
key returns the original job instead of billing a second time.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\": \"Things Fall Apart\", \"author\": \"Chinua Achebe\", \"form\": \"unknown\", \"reader\": \"a book group meeting on Thursday\", \"ask\": \"the major themes and how the book is structured\", \"spoilers\": \"avoid\", \"depth\": \"standard\", \"sources\": [{\"id\": \"SRC-1\", \"title\": \"Things Fall Apart\", \"abstract\": \"Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.\", \"url\": \"https://en.wikipedia.org/wiki/Things_Fall_Apart\"}], \"page_facts\": {\"records_retrieved\": 1, \"form_read_as\": \"novel\", \"form_read_from\": \"the retrieved abstract\", \"short_form\": false}, \"ask_flags\": []}"import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/run",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])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({
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
})
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
payload := `{"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}`
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewBufferString(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String payload = """
{
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
}""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
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 = {"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}.to_s
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$payload = '{"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/run",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $payload,
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var payload = new StringContent(@"{""title"": ""Things Fall Apart"", ""author"": ""Chinua Achebe"", ""form"": ""unknown"", ""reader"": ""a book group meeting on Thursday"", ""ask"": ""the major themes and how the book is structured"", ""spoilers"": ""avoid"", ""depth"": ""standard"", ""sources"": [{""id"": ""SRC-1"", ""title"": ""Things Fall Apart"", ""abstract"": ""Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958."", ""url"": ""https://en.wikipedia.org/wiki/Things_Fall_Apart""}], ""page_facts"": {""records_retrieved"": 1, ""form_read_as"": ""novel"", ""form_read_from"": ""the retrieved abstract"", ""short_form"": false}, ""ask_flags"": []}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run", payload);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Then poll the job until it reaches a terminal state:
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/jobs/job_123" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN"import requests
TOKEN = "YOUR_TOKEN"
r = requests.get(
"https://api.skillsafe.ai/v1/app-api/jobs/job_123",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/jobs/job_123", {
method: "GET",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
}
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/jobs/job_123", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/jobs/job_123"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
TOKEN = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/jobs/job_123")
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 JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/jobs/job_123",
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var res = await http.GetAsync("https://api.skillsafe.ai/v1/app-api/jobs/job_123");
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
A terminal job carries status, output.output (the model's reply as a
string), charged_credits and truncated. If truncated is
true the run hit the balance-derived output cap and the reply is genuinely incomplete —
parse what arrived rather than discarding it.
6. Stream it instead
/run-stream is the same call over SSE. Events are job,
delta and done. Long briefs are worth streaming.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\": \"Things Fall Apart\", \"author\": \"Chinua Achebe\", \"form\": \"unknown\", \"reader\": \"a book group meeting on Thursday\", \"ask\": \"the major themes and how the book is structured\", \"spoilers\": \"avoid\", \"depth\": \"standard\", \"sources\": [{\"id\": \"SRC-1\", \"title\": \"Things Fall Apart\", \"abstract\": \"Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.\", \"url\": \"https://en.wikipedia.org/wiki/Things_Fall_Apart\"}], \"page_facts\": {\"records_retrieved\": 1, \"form_read_as\": \"novel\", \"form_read_from\": \"the retrieved abstract\", \"short_form\": false}, \"ask_flags\": []}"import requests
TOKEN = "YOUR_TOKEN"
r = requests.post(
"https://api.skillsafe.ai/v1/app-api/run-stream",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
},
timeout=120,
)
r.raise_for_status()
print(r.json()["data"])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({
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
})
});
const { data, error } = await res.json();
if (error) throw new Error(error.message);
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
payload := `{"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}`
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run-stream", bytes.NewBufferString(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String payload = """
{
"title": "Things Fall Apart",
"author": "Chinua Achebe",
"form": "unknown",
"reader": "a book group meeting on Thursday",
"ask": "the major themes and how the book is structured",
"spoilers": "avoid",
"depth": "standard",
"sources": [
{
"id": "SRC-1",
"title": "Things Fall Apart",
"abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.",
"url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"
}
],
"page_facts": {
"records_retrieved": 1,
"form_read_as": "novel",
"form_read_from": "the retrieved abstract",
"short_form": false
},
"ask_flags": []
}""";
HttpRequest req = HttpRequest.newBuilder()
.uri(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(payload))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require "net/http"
require "json"
require "uri"
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 = {"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}.to_s
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = "YOUR_TOKEN";
$payload = '{"title": "Things Fall Apart", "author": "Chinua Achebe", "form": "unknown", "reader": "a book group meeting on Thursday", "ask": "the major themes and how the book is structured", "spoilers": "avoid", "depth": "standard", "sources": [{"id": "SRC-1", "title": "Things Fall Apart", "abstract": "Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958.", "url": "https://en.wikipedia.org/wiki/Things_Fall_Apart"}], "page_facts": {"records_retrieved": 1, "form_read_as": "novel", "form_read_from": "the retrieved abstract", "short_form": false}, "ask_flags": []}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.skillsafe.ai/v1/app-api/run-stream",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $token,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $payload,
]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var token = "YOUR_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var payload = new StringContent(@"{""title"": ""Things Fall Apart"", ""author"": ""Chinua Achebe"", ""form"": ""unknown"", ""reader"": ""a book group meeting on Thursday"", ""ask"": ""the major themes and how the book is structured"", ""spoilers"": ""avoid"", ""depth"": ""standard"", ""sources"": [{""id"": ""SRC-1"", ""title"": ""Things Fall Apart"", ""abstract"": ""Things Fall Apart is the debut novel by Nigerian author Chinua Achebe, first published in 1958."", ""url"": ""https://en.wikipedia.org/wiki/Things_Fall_Apart""}], ""page_facts"": {""records_retrieved"": 1, ""form_read_as"": ""novel"", ""form_read_from"": ""the retrieved abstract"", ""short_form"": false}, ""ask_flags"": []}", Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run-stream", payload);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
7. The output contract
The reply is a single JSON object — no code fence, no prose around it. Every key is
always present; unused sections are "" or [], never
null and never absent.
{
"found": true,
"confidence": "verified | recalled | uncertain",
"not_found": {"reason": "", "similar_works": [{"title","author","note"}], "what_would_help": ""},
"work": {
"title": "", "title_source": "",
"author": "", "author_source": "",
"first_published": "", "first_published_source": "",
"original_language": "", "original_language_source": "",
"form": "", "form_source": "",
"series": "", "series_source": "",
"length_note": ""
},
"orientation": "", "premise": "", "argument": "",
"structure": [{"part": "", "role": "", "note": ""}],
"themes": [{"theme": "", "how_it_works": "", "where_it_lives": ""}],
"context": {"historical": "", "biographical": "", "publication": ""},
"reception": {"on_publication": "", "since": "", "contested": ""},
"style": "",
"difficulty": {"band": "accessible | moderate | demanding | specialist | ", "why": "", "time_note": ""},
"for_you": {"read_it_if": [], "skip_it_if": [], "before_you_start": ""},
"read_next": [{"title": "", "author": "", "relation": ""}],
"quotations": [{"text": "", "attribution": "", "supports": ""}],
"editions_note": "", "spoiler_note": "", "limits": "",
"declined": [{"asked": "", "why": ""}]
}
The _source fields
Each of the five volatile facts is paired with a _source holding either the
id of a record you sent (SRC-1) or the literal string
model-knowledge. Those five — author, year, original language, form, series
— are the facts a language model invents most readily and a reader cannot check. A
non-empty value beside an empty _source is a defect in the reply.
found: false
When the work cannot be identified, found is false,
not_found is filled in with real similar-titled works, and
every other section is empty. A reply carrying both found: false
and an orientation is self-contradicting, and the orientation is the half to distrust. Handle
this branch explicitly: it is the one that protects your users from a confidently invented book.
difficulty.band
Normally one of the four bands, but legitimately an empty string when found is
false — rating a book that could not be identified would be exactly the kind
of confident filler this app avoids. Do not validate it as a closed enum.
Rate limits worth knowing
/search: 30 requests per minute per IP, and 500 upstream fetches per app per day. Results are cached for 24 hours per provider and query, and cache hits do not count.- Vector search over saved briefs: 30 per minute per IP.
- Runs are governed by your credit balance rather than a request ceiling.
A brief is a starting point for reading, not a reference work and not a citable scholarly source. Treat the confirmed marks as meaning "checked against a retrieved snippet", which is a real claim but a modest one.